Fragment 有很多種使用方法,官方并沒有提供一個統(tǒng)一的 api 來處理 Fragment 的可見性判斷和回調(diào)重慢,導(dǎo)致在不同的使用場景下需要使用不同的方法來判斷 Fragment 的可見性畸写。網(wǎng)上已經(jīng)有很多講 Fragment 可見性的文章藏雏,但是大部分文章覆蓋的使用場景不夠全面涯塔,有些文章的用法也過時了鹤啡,因此本人梳理了當(dāng)前 Fragment 的各種使用場景惯驼,提供了一個統(tǒng)一的 api 來處理 Fragment 的可見性。
一般使用場景
在Activity中直接使用
在 xml 文件中聲明 Fragment递瑰,或者在代碼中通過 FragmentTransaction 的 add 或 replace 動態(tài)載入 Fragment祟牲。這兩種情況下都只要監(jiān)聽 Fragment 的 onResume 和 onPause 方法就能判斷 Fragment 的可見性。
override fun onResume() {
super.onResume()
determineFragmentVisible()
}
override fun onPause() {
super.onPause()
determineFragmentInvisible()
}
使用show和hide控制顯示和隱藏
Google 在
androidx.fragment 1.2.0
中新增了一個 FragmentContainerView抖部,用來替代 FlameLayout 做為 Fragment 的容器说贝,在下文中將使用 FragmentContainerView 作為 Fragment 的容器。
老的用法
通過 FragmentTransaction 的 add 將 Fragment 添加到 FragmentManager 后慎颗,F(xiàn)ragment 的生命周期會跟隨綁定的 Activity 或父 Fragment 走到 onResume乡恕,這個時候,只要所依附的 Activity 或父 Fragment 的生命周期不發(fā)生變化俯萎,通過 FragmentTransaction 的 show 和 hide 方法控制 Fragment 的顯示和隱藏并不會改變 Fragment 的生命周期几颜,這個時候需要監(jiān)聽 onHiddenChanged 判斷 Fragment 的可見性。
一般情況下讯屈,將 Fragment add 到 FragmentManager 的過程是在 Activity 中的 onCreate 回調(diào)中進行的蛋哭,第一次回調(diào) onHiddenChanged 是在 Fragment 回調(diào) onCreateView 之前。如果需要在 Fragment 第一次可見的時候進行 UI 操作涮母,就會出錯谆趾,為了避免出錯,需要結(jié)合 Fragment 的 onResume 和 onPause 判斷 Fragment 的可見性叛本。
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
if (hidden) {
determineFragmentInvisible()
} else {
determineFragmentVisible()
}
}
override fun onResume() {
super.onResume()
determineFragmentVisible()
}
override fun onPause() {
super.onPause()
determineFragmentInvisible()
}
AndroidX用法
調(diào)用了 hide 后沪蓬,接著調(diào)用 setMaxLifecycle(fragment, Lifecycle.State.STARTED)
,F(xiàn)ragment 生命周期會走到 onPause来候。調(diào)用 show 方法后跷叉,接著調(diào)用 setMaxLifecycle(fragment, Lifecycle.State.RESUMED)
,F(xiàn)ragment 生命周期會走到 onPause营搅。這樣只要監(jiān)聽 Fragment 的 onResume 和 onPause 方法就能判斷 Fragment 的可見性云挟。
override fun onResume() {
super.onResume()
determineFragmentVisible()
}
override fun onPause() {
super.onPause()
determineFragmentInvisible()
}
在ViewPager中使用
老的用法
在 support 和 androidx.fragment 1.0.0
,通過監(jiān)聽 setUserVisibleHint 判斷Fragment 的可見性转质。如果將 Fragment add 到 FragmentManager 的過程是在 Activity 中的 onCreate 回調(diào)中進行的园欣,第一次回調(diào) setUserVisibleHint 也是在 Fragment 回調(diào) onCreateView 之前,也需要結(jié)合 Fragment 的 onResume 和 onPause 判斷 Fragment 的可見性休蟹。
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser) {
determineFragmentVisible()
} else {
determineFragmentInvisible()
}
}
override fun onResume() {
super.onResume()
determineFragmentVisible()
}
override fun onPause() {
super.onPause()
determineFragmentInvisible()
}
AndroidX用法
谷歌從 androidx.fragment 1.1.0
中開始沸枯,對 FragmentPagerAdapter 和 FragmentStatePagerAdapter 進行了調(diào)整日矫,支持使用 setMaxLifecycle 控制 Fragment 的生命周期,只需要創(chuàng)建 Adpter 的時候绑榴, Behavior 選擇 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
哪轿。
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
...
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
...
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
...
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
...
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true);
}
...
}
}
這樣只要監(jiān)聽 Fragment 的 onResume 和 onPause 方法就能判斷 Fragment 的可見性。
override fun onResume() {
super.onResume()
determineFragmentVisible()
}
override fun onPause() {
super.onPause()
determineFragmentInvisible()
}
在ViewPager2中使用
在 ViewPager2 中使用 Fragment 時翔怎,使用的適配器是 FragmentStateAdapter缔逛,F(xiàn)ragmentStateAdapter 內(nèi)部使用 FragmentMaxLifecycleEnforcer ,F(xiàn)ragmentMaxLifecycleEnforcer 也是通過 setMaxLifecycle 控制 Fragment 的生命周期
class FragmentStateAdapter {
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
...
mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
...
}
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
...
mFragmentMaxLifecycleEnforcer = null;
}
class FragmentMaxLifecycleEnforcer {
void updateFragmentMaxLifecycle(boolean dataSetChanged) {
...
for (int ix = 0; ix < mFragments.size(); ix++) {
...
if (itemId != mPrimaryItemId) {
transaction.setMaxLifecycle(fragment, STARTED);
} else {
toResume = fragment; // itemId map key, so only one can match the predicate
}
...
}
if (toResume != null) { // in case the Fragment wasn't added yet
transaction.setMaxLifecycle(toResume, RESUMED);
}
...
}
}
}
這樣只要監(jiān)聽 Fragment 的 onResume 和 onPause 方法就能判斷 Fragment 的可見性姓惑。
override fun onResume() {
super.onResume()
determineFragmentVisible()
}
override fun onPause() {
super.onPause()
determineFragmentInvisible()
}
具體實現(xiàn)
在 IFragmentVisibility
中定義 Fragment 可見性相關(guān)方法:
interface IFragmentVisibility {
/**
* Fragment可見時調(diào)用。
*/
fun onVisible() {}
/**
* Fragment不可見時調(diào)用按脚。
*/
fun onInvisible() {}
/**
* Fragment第一次可見時調(diào)用于毙。
*/
fun onVisibleFirst() {}
/**
* Fragment可見時(第一次除外)調(diào)用。
*/
fun onVisibleExceptFirst() {}
/**
* Fragment當(dāng)前是否對用戶可見
*/
fun isVisibleToUser(): Boolean
}
Fragment可見
Fragment 可見受到幾個因素影響:Fragment 是否處于 RESUMED 狀態(tài)辅搬、Fragment 是否顯示唯沮、Fragment Hint 是否對用戶可見,判斷Fragment可見性可能會被連續(xù)調(diào)用多次堪遂,如果當(dāng)前已經(jīng)對用戶可見介蛉,則不進行判斷可見性。
// Fragment當(dāng)前是否對用戶可見溶褪。
private var mIsFragmentVisible = false
// Fragment當(dāng)前是否是第一次對用戶可見币旧。
private var mIsFragmentVisibleFirst = true
private fun determineFragmentVisible() {
if (isResumed && !isHidden && userVisibleHint && !mIsFragmentVisible) {
mIsFragmentVisible = true
onVisible()
if (mIsFragmentVisibleFirst) {
mIsFragmentVisibleFirst = false
onVisibleFirst()
} else {
onVisibleExceptFirst()
}
}
}
Fragment不可見
當(dāng) Fragment 處于可見狀態(tài),調(diào)用一次 determineFragmentInvisible 方法猿妈,F(xiàn)ragment 就變成不可見了吹菱。
private fun determineFragmentInvisible() {
if (mIsFragmentVisible) {
mIsFragmentVisible = false
onInvisible()
}
}
Fragment嵌套
老的用法
從日志中可以看到,F(xiàn)ragment-1 和 Fragment-1-1 處于可見狀態(tài)彭则,但是奇怪的是 Fragment-2-1 也處于可見狀態(tài)鳍刷,這不符合邏輯,判斷可見性邏輯還有待優(yōu)化的地方俯抖。
分析日志可知输瓜,所有的 Fragment 生命周期都走到了onResume,但是 Fragment-2芬萍、Fragment-1-2尤揣、Fragment-2-2 因為 isHidden = true
,判斷出是不可見狀態(tài)柬祠。Fragment-2-1 是 isHidden = false
芹缔,但是 Fragment-2 是 isHidden = true
,從邏輯上父 Fragment 不可見瓶盛,子 Fragment 也應(yīng)該不可見最欠。所以在判斷 Fragment 是否可見的時候示罗,還要考慮父 Fragment 是否可見(如果存在父 Fragment)。
當(dāng)從 Fragment-1 切換到 Fragment-2 后芝硬,可以看到蚜点,F(xiàn)ragment-1 不可見,F(xiàn)ragment-2 可見拌阴,但是本應(yīng)該不可見的 Fragment-1-1 還是可見绍绘,本應(yīng)該可見的 Fragment-2-1 還是不可見,說明判斷可見性邏輯還有待優(yōu)化的地方迟赃。
從 Fragment-1 切換到 Fragment-2陪拘,這兩者的 onHiddenChanged 被調(diào)用了,所以它們的可見性發(fā)生了變化纤壁。Fragment-1-1 和 Fragment-2-1 沒有任何操作左刽,但是它們的可見性也應(yīng)該隨著父Fragment 可見性發(fā)生變化而變化,所以應(yīng)該在父 Fragment 可見性變化的時候重新判斷一次子 Fragment 的可見性酌媒。
AndroidX用法
全部使用 setMaxLifecycle 控制 Fragment 生命周期欠痴,可以看到 Fragment 的可見性判斷是正確的。
從 Fragment-1 切換到 Fragment-2秒咨,可見性判斷還是正確的喇辽。
子 Fragment 的生命周期會根據(jù)所綁定的 Activity 或父 Fragment 的生命周期變化而變化,setMaxLifecycle 改變了父 Fragment 的生命周期雨席,子 Fragment 的生命周期自然就跟著變化了菩咨。所以,僅監(jiān)聽 Fragment 的 onResume 和 onPause 就能判斷 Fragment 的可見性陡厘,不需要調(diào)整判斷邏輯旦委。
具體實現(xiàn)
在 determineFragmentVisible 中增加判斷父 Fragment 是否可見的代碼:
private fun determineFragmentVisible() {
val parent = parentFragment
if (parent != null && parent is VisibilityFragment) {
if (!parent.isVisibleToUser()) {
// 父Fragment不可見,子Fragment也一定不可見
return
}
}
...
}
在 determineFragmentVisible 和 determineFragmentInvisible 增加判斷子 Fragment 的可見性代碼:
private fun determineFragmentVisible() {
...
if (isResumed && !isHidden && userVisibleHint && !mIsFragmentVisible) {
...
determineChildFragmentVisible()
}
}
private fun determineFragmentInvisible() {
if (mIsFragmentVisible) {
...
determineChildFragmentInvisible()
}
}
private fun determineChildFragmentVisible() {
childFragmentManager.fragments.forEach {
if (it is VisibilityFragment) {
it.determineFragmentVisible()
}
}
}
private fun determineChildFragmentInvisible() {
childFragmentManager.fragments.forEach {
if (it is VisibilityFragment) {
it.determineFragmentInvisible()
}
}
}
懶加載
在實現(xiàn)了上述功能后雏亚,對于需要懶加載功能的 Fragment缨硝,只需要重寫 onVisibleFirst,在里面加載數(shù)據(jù)就可以了罢低。
總結(jié)
對于全部使用 setMaxLifecycle 控制 Fragment 生命周期的代碼查辩,F(xiàn)ragment 的可見性判斷相對比較簡單,只要監(jiān)聽 Fragment 的 onResume 和 onPause 方法就能判斷 Fragment 的可見性网持。
對于老的用法或者老的用法和 setMaxLifecycle 混用的代碼宜岛,F(xiàn)ragment 可見性判斷不僅要考慮使用方式,也要考慮父 Fragment 的可見性功舀,同時自身可見性改變的時候萍倡,也要主動調(diào)用子 Fragment 判斷可見性的代碼。
項目地址
fragment-visibility辟汰,覺得用起來很爽的列敲,請不要吝嗇你的 Star 阱佛!