概述
相信很多使用过 Fragment 的朋友都对判断 Fragment 是否对用户可见有此疑问,网上有很多文章也介绍得比较片面,只覆盖到了其中一种情况。我在项目中也有遇到这样的问题,经过试验和积累,已经总结出判断的方法并进行了封装,在这里给大家简单介绍下。
先说说几个重要的函数
1. setUserVisibleHint
网上很多对这个方法的说明,这个方法只会在 ViewPager 和 FragmentPagerAdapter一起使用时才会触发。我们可以通过 getUserVisibleHint 来得到这个状态。
看下源码的说明:
/**
* 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 是在一定场景下才会使用的,单纯用 getUserVisibleHint 来判断可见是不对的。 这仅仅适用于 ViewPager 的情况。
2. onHiddenChanged
onHiddenChanged 方法是在使用 show/hide 方法时会触发。来看下源码的说明:
/**
* 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 的显示状态时,会触发此函数,并且可以通过 isHidden() 来获取当前显示隐藏的状态。
说说我项目中的封装
我在 BaseFragment 中封装了onVisible();和onInvisible();两个回调,业务只需要覆写这两个方法就能根据 Fragment 可见状态的改变来写逻辑。
PS:这里说明一下,我封装的可见不可见回调,是在状态改变的时候回调的。如果已经是 hide 不可见了,再执行 onPause 方法时我就不会触发 onInvisible 的回调了,所以业务端可以根据回调进行逻辑处理。
直接看代码吧,代码中有详细的注释说明,这里就不多说了。
/**
* 当fragment与viewpager、FragmentPagerAdapter一起使用时,切换页面时会调用此方法
*
* @param isVisibleToUser 是否对用户可见
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
boolean change = isVisibleToUser != getUserVisibleHint();
super.setUserVisibleHint(isVisibleToUser);
// 在viewpager中,创建fragment时就会调用这个方法,但这时还没有resume,为了避免重复调用visible和invisible,
// 只有当fragment状态是resumed并且初始化完毕后才进行visible和invisible的回调
if (isResumed() && change) {
if (getUserVisibleHint()) {
onVisible();
} else {
onInvisible();
}
}
}
/**
* 当使用show/hide方法时,会触发此回调
*
* @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时,getUserVisibleHint默认为true
// 如果是其它情况,就通过isHidden判断,因为show/hide时会改变isHidden的状态
// 所以,只有当fragment原来是可见状态时,进入onResume就回调onVisible
if (getUserVisibleHint() && !isHidden()) {
onVisible();
}
}
@Override
public void onPause() {
super.onPause();
// onPause时也需要判断,如果当前fragment在viewpager中不可见,就已经回调过了,onPause时也就不需要再次回调onInvisible了
// 所以,只有当fragment是可见状态时进入onPause才加调onInvisible
if (getUserVisibleHint() && !isHidden()) {
onInvisible();
}
}
还是用一些场景来说明一下吧。
- 如果一个Fragment跳转到另一个 Activity,回来时会调用 Fragment 的 onResume 方法,这时,由于不在 ViewPager 中,getUserVisibleHint 默认是返回 true 的,那 Fragment 是否可见就依赖于 isHidden 方法了,如果跳转时是可见,那 isHidden 就是 false,执行 onVisible 回调,如果 跳转时不可见,那 isHidden 就是 true,那么就不会回调 onVisible了。
- 如果是在 ViewPager 中,因为 ViewPager 会自动调用 setUserVisibleHint 方法来改变可见状态,如果不在 onResume 中增加判断,会导致从别的 Activity 回来后,ViewPager 里所有的 Fragment 都执行 onVisible 回调了,实际上 ViewPager 只有一个Fragment 是当前可见的。
关于 setUserVisibleHint 还是多说几句,代码中有判断 isResumed ,是因为在ViewPager中,一创建 Fragment 时就调用了 setUserVisibleHint 方法,此时回调可见不可见是不合适的,因为还没有把 View 创建好,所以增加了 isResumed 判断,因为在 onResume 时也会进行判断并且回调的,也避免了重复调用 onVisible 和 onInvisible。
总结
判断 Fragment 对用户是否可见还是依赖于 getUserVisibleHint 和 isHidden 这两个重要方法的。这里也需要去理解 ViewPager 里 setUserVisibleHint 的作用,它只是把在屏幕外的 Fragment 加了一个标识,因为它也是被加到 window 中了,也是 onResume 状态了,所以用了一个新标识去表明不在屏幕内,标识为不可见。所以要结合判断,不能只判断其中一个。