概述
相信很多使用過 Fragment 的朋友都對(duì)判斷 Fragment 是否對(duì)用戶可見有此疑問,網(wǎng)上有很多文章也介紹得比較片面,只覆蓋到了其中一種情況。我在項(xiàng)目中也有遇到這樣的問題,經(jīng)過試驗(yàn)和積累,已經(jīng)總結(jié)出判斷的方法并進(jìn)行了封裝,在這里給大家簡(jiǎn)單介紹下。
先說說幾個(gè)重要的函數(shù)
- setUserVisibleHint
網(wǎng)上很多對(duì)這個(gè)方法的說明,這個(gè)方法只會(huì)在 ViewPager 和 FragmentPagerAdapter一起使用時(shí)才會(huì)觸發(fā)。我們可以通過 getUserVisibleHint 來得到這個(gè)狀態(tài)。
看下源碼的說明:
/**
* 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;
}
上面說了,setUserVisibleHint 是在一定場(chǎng)景下才會(huì)使用的,單純用 getUserVisibleHint 來判斷可見是不對(duì)的。 這僅僅適用于 ViewPager 的情況。
- onHiddenChanged
onHiddenChanged 方法是在使用 show/hide 方法時(shí)會(huì)觸發(fā)。來看下源碼的說明:
/**
* 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ù),并且可以通過 isHidden() 來獲取當(dāng)前顯示隱藏的狀態(tài)。
說說我項(xiàng)目中的封裝
我在 BaseFragment 中封裝了onVisible();和onInvisible();兩個(gè)回調(diào),業(yè)務(wù)只需要覆寫這兩個(gè)方法就能根據(jù) Fragment 可見狀態(tài)的改變來寫邏輯。
PS:這里說明一下,我封裝的可見不可見回調(diào),是在狀態(tài)改變的時(shí)候回調(diào)的。如果已經(jīng)是 hide 不可見了,再執(zhí)行 onPause 方法時(shí)我就不會(huì)觸發(fā) onInvisible 的回調(diào)了,所以業(yè)務(wù)端可以根據(jù)回調(diào)進(jìn)行邏輯處理。
直接看代碼吧,代碼中有詳細(xì)的注釋說明,這里就不多說了。
/**
* 當(dāng)fragment與viewpager、FragmentPagerAdapter一起使用時(shí),切換頁面時(shí)會(huì)調(diào)用此方法
*
* @param isVisibleToUser 是否對(duì)用戶可見
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
boolean change = isVisibleToUser != getUserVisibleHint();
super.setUserVisibleHint(isVisibleToUser);
// 在viewpager中,創(chuàng)建fragment時(shí)就會(huì)調(diào)用這個(gè)方法,但這時(shí)還沒有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可見
// 如果是在viewpager里,就需要判斷getUserVisibleHint,不在viewpager時(shí),getUserVisibleHint默認(rèn)為true
// 如果是其它情況,就通過isHidden判斷,因?yàn)閟how/hide時(shí)會(huì)改變isHidden的狀態(tài)
// 所以,只有當(dāng)fragment原來是可見狀態(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中不可見,就已經(jīng)回調(diào)過了,onPause時(shí)也就不需要再次回調(diào)onInvisible了
// 所以,只有當(dāng)fragment是可見狀態(tài)時(shí)進(jìn)入onPause才加調(diào)onInvisible
if (getUserVisibleHint() && !isHidden()) {
onInvisible();
}
}
還是用一些場(chǎng)景來說明一下吧。
如果一個(gè)Fragment跳轉(zhuǎn)到另一個(gè) Activity,回來時(shí)會(huì)調(diào)用 Fragment 的 onResume 方法,這時(shí),由于不在 ViewPager 中,getUserVisibleHint 默認(rèn)是返回 true 的,那 Fragment 是否可見就依賴于 isHidden 方法了,如果跳轉(zhuǎn)時(shí)是可見,那 isHidden 就是 false,執(zhí)行 onVisible 回調(diào),如果 跳轉(zhuǎn)時(shí)不可見,那 isHidden 就是 true,那么就不會(huì)回調(diào) onVisible了。
如果是在 ViewPager 中,因?yàn)?ViewPager 會(huì)自動(dòng)調(diào)用 setUserVisibleHint 方法來改變可見狀態(tài),如果不在 onResume 中增加判斷,會(huì)導(dǎo)致從別的 Activity 回來后,ViewPager 里所有的 Fragment 都執(zhí)行 onVisible 回調(diào)了,實(shí)際上 ViewPager 只有一個(gè)Fragment 是當(dāng)前可見的。
關(guān)于 setUserVisibleHint 還是多說幾句,代碼中有判斷 isResumed ,是因?yàn)樵赩iewPager中,一創(chuàng)建 Fragment 時(shí)就調(diào)用了 setUserVisibleHint 方法,此時(shí)回調(diào)可見不可見是不合適的,因?yàn)檫€沒有把 View 創(chuàng)建好,所以增加了 isResumed 判斷,因?yàn)樵?onResume 時(shí)也會(huì)進(jìn)行判斷并且回調(diào)的,也避免了重復(fù)調(diào)用 onVisible 和 onInvisible。
總結(jié)
判斷 Fragment 對(duì)用戶是否可見還是依賴于 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é)合判斷,不能只判斷其中一個(gè)。