概述
相信很多使用過(guò) Fragment 的朋友都對(duì)判斷 Fragment 是否對(duì)用戶可見(jiàn)有此疑問(wèn),網(wǎng)上有很多文章也介紹得比較片面,只覆蓋到了其中一種情況。我在項(xiàng)目中也有遇到這樣的問(wèn)題,經(jīng)過(guò)試驗(yàn)和積累,已經(jīng)總結(jié)出判斷的方法并進(jìn)行了封裝,在這里給大家簡(jiǎn)單介紹下。
先說(shuō)說(shuō)幾個(gè)重要的函數(shù)
1. setUserVisibleHint
網(wǎng)上很多對(duì)這個(gè)方法的說(shuō)明,這個(gè)方法只會(huì)在 ViewPager 和 FragmentPagerAdapter一起使用時(shí)才會(huì)觸發(fā)。我們可以通過(guò) getUserVisibleHint 來(lái)得到這個(gè)狀態(tài)。
看下源碼的說(shuō)明:
/**
* Set a hint to the system about whether this fragment's UI is currently visible
* to the user. This hint defaults to true and is persistent across fragment instance
* state save and restore.
*
* <p>An app may set this to false to indicate that the fragment's UI is
* scrolled out of visibility or is otherwise not directly visible to the user.
* This may be used by the system to prioritize operations such as fragment lifecycle updates
* or loader ordering behavior.</p>
*
* <p><strong>Note:</strong> This method may be called outside of the fragment lifecycle.
* and thus has no ordering guarantees with regard to fragment lifecycle method calls.</p>
*
* @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
* false if it is not.
*/
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
&& mFragmentManager != null && isAdded()) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
mDeferStart = mState < STARTED && !isVisibleToUser;
}
/**
* @return The current value of the user-visible hint on this fragment.
* @see #setUserVisibleHint(boolean)
*/
public boolean getUserVisibleHint() {
return mUserVisibleHint;
}
上面說(shuō)了,setUserVisibleHint 是在一定場(chǎng)景下才會(huì)使用的,單純用 getUserVisibleHint 來(lái)判斷可見(jiàn)是不對(duì)的。 這僅僅適用于 ViewPager 的情況。
2. onHiddenChanged
onHiddenChanged 方法是在使用 show/hide 方法時(shí)會(huì)觸發(fā)。來(lái)看下源碼的說(shuō)明:
/**
* Called when the hidden state (as returned by {@link #isHidden()} of
* the fragment has changed. Fragments start out not hidden; this will
* be called whenever the fragment changes state from that.
* @param hidden True if the fragment is now hidden, false otherwise.
*/
public void onHiddenChanged(boolean hidden) {
}
在我們使用show(fragment)和 hide(fragment)改變了 fragment 的顯示狀態(tài)時(shí),會(huì)觸發(fā)此函數(shù),并且可以通過(guò) isHidden() 來(lái)獲取當(dāng)前顯示隱藏的狀態(tài)。
說(shuō)說(shuō)我項(xiàng)目中的封裝
我在 BaseFragment 中封裝了onVisible();和onInvisible();兩個(gè)回調(diào),業(yè)務(wù)只需要覆寫(xiě)這兩個(gè)方法就能根據(jù) Fragment 可見(jiàn)狀態(tài)的改變來(lái)寫(xiě)邏輯。
PS:這里說(shuō)明一下,我封裝的可見(jiàn)不可見(jiàn)回調(diào),是在狀態(tài)改變的時(shí)候回調(diào)的。如果已經(jīng)是 hide 不可見(jiàn)了,再執(zhí)行 onPause 方法時(shí)我就不會(huì)觸發(fā) onInvisible 的回調(diào)了,所以業(yè)務(wù)端可以根據(jù)回調(diào)進(jìn)行邏輯處理。
直接看代碼吧,代碼中有詳細(xì)的注釋說(shuō)明,這里就不多說(shuō)了。
/**
* 當(dāng)fragment與viewpager、FragmentPagerAdapter一起使用時(shí),切換頁(yè)面時(shí)會(huì)調(diào)用此方法
*
* @param isVisibleToUser 是否對(duì)用戶可見(jiàn)
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
boolean change = isVisibleToUser != getUserVisibleHint();
super.setUserVisibleHint(isVisibleToUser);
// 在viewpager中,創(chuàng)建fragment時(shí)就會(huì)調(diào)用這個(gè)方法,但這時(shí)還沒(méi)有resume,為了避免重復(fù)調(diào)用visible和invisible,
// 只有當(dāng)fragment狀態(tài)是resumed并且初始化完畢后才進(jìn)行visible和invisible的回調(diào)
if (isResumed() && change) {
if (getUserVisibleHint()) {
onVisible();
} else {
onInvisible();
}
}
}
/**
* 當(dāng)使用show/hide方法時(shí),會(huì)觸發(fā)此回調(diào)
*
* @param hidden fragment是否被隱藏
*/
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
onInvisible();
} else {
onVisible();
}
}
@Override
public void onResume() {
super.onResume();
// onResume并不代表fragment可見(jiàn)
// 如果是在viewpager里,就需要判斷getUserVisibleHint,不在viewpager時(shí),getUserVisibleHint默認(rèn)為true
// 如果是其它情況,就通過(guò)isHidden判斷,因?yàn)閟how/hide時(shí)會(huì)改變isHidden的狀態(tài)
// 所以,只有當(dāng)fragment原來(lái)是可見(jiàn)狀態(tài)時(shí),進(jìn)入onResume就回調(diào)onVisible
if (getUserVisibleHint() && !isHidden()) {
onVisible();
}
}
@Override
public void onPause() {
super.onPause();
// onPause時(shí)也需要判斷,如果當(dāng)前fragment在viewpager中不可見(jiàn),就已經(jīng)回調(diào)過(guò)了,onPause時(shí)也就不需要再次回調(diào)onInvisible了
// 所以,只有當(dāng)fragment是可見(jiàn)狀態(tài)時(shí)進(jìn)入onPause才加調(diào)onInvisible
if (getUserVisibleHint() && !isHidden()) {
onInvisible();
}
}
還是用一些場(chǎng)景來(lái)說(shuō)明一下吧。
- 如果一個(gè)Fragment跳轉(zhuǎn)到另一個(gè) Activity,回來(lái)時(shí)會(huì)調(diào)用 Fragment 的 onResume 方法,這時(shí),由于不在 ViewPager 中,getUserVisibleHint 默認(rèn)是返回 true 的,那 Fragment 是否可見(jiàn)就依賴于 isHidden 方法了,如果跳轉(zhuǎn)時(shí)是可見(jiàn),那 isHidden 就是 false,執(zhí)行 onVisible 回調(diào),如果 跳轉(zhuǎn)時(shí)不可見(jiàn),那 isHidden 就是 true,那么就不會(huì)回調(diào) onVisible了。
- 如果是在 ViewPager 中,因?yàn)?ViewPager 會(huì)自動(dòng)調(diào)用 setUserVisibleHint 方法來(lái)改變可見(jiàn)狀態(tài),如果不在 onResume 中增加判斷,會(huì)導(dǎo)致從別的 Activity 回來(lái)后,ViewPager 里所有的 Fragment 都執(zhí)行 onVisible 回調(diào)了,實(shí)際上 ViewPager 只有一個(gè)Fragment 是當(dāng)前可見(jiàn)的。
關(guān)于 setUserVisibleHint 還是多說(shuō)幾句,代碼中有判斷 isResumed ,是因?yàn)樵赩iewPager中,一創(chuàng)建 Fragment 時(shí)就調(diào)用了 setUserVisibleHint 方法,此時(shí)回調(diào)可見(jiàn)不可見(jiàn)是不合適的,因?yàn)檫€沒(méi)有把 View 創(chuàng)建好,所以增加了 isResumed 判斷,因?yàn)樵?onResume 時(shí)也會(huì)進(jìn)行判斷并且回調(diào)的,也避免了重復(fù)調(diào)用 onVisible 和 onInvisible。
總結(jié)
判斷 Fragment 對(duì)用戶是否可見(jiàn)還是依賴于 getUserVisibleHint 和 isHidden 這兩個(gè)重要方法的。這里也需要去理解 ViewPager 里 setUserVisibleHint 的作用,它只是把在屏幕外的 Fragment 加了一個(gè)標(biāo)識(shí),因?yàn)樗彩潜患拥?window 中了,也是 onResume 狀態(tài)了,所以用了一個(gè)新標(biāo)識(shí)去表明不在屏幕內(nèi),標(biāo)識(shí)為不可見(jiàn)。所以要結(jié)合判斷,不能只判斷其中一個(gè)。