前言
在项目中我们可能需要获取Fragment可见或者不可见时的回调。(回调这个词在这里用的可能并不准确,这里理解为当Fragment可见或者不可见时能够触发一些方法,下同)。
Fragmeng生命周期中有onResume
,onPause
,这两个生命周期是跟随Activity的。当调用getSupportFragmentManager().beginTransaction().hide(fragment)
时或者滑动ViewPager隐藏Fragment时,Fragment的这两个生命周期都不会回调。
那么,如何得到Fragment可见时的回调呢?如何得到Fragment不可见时的回调呢?
思路
首先先定义一个BetterLifecycleFragment继承自Fragment。我希望当Fragment可见状态改变时,子类可以有对应的生命周期的回调。如 onFragmentResume, onFragmentPause。如下:
public class BetterLifecycleFragment extends Fragment {
/**
* Fragment 可见时回调
* @param isFirst 是否是第一次显示
*/
protected void onFragmentResume(boolean isFirst){
}
/**
* Fragment 不可见时回调
*/
protected void onFragmentPause(){
}
}
注意到这里 onFragmentResume 里有一个 isFirst参数,这个参数是为了方便懒加载。如果是第一次,则去请求数据。
统计Fragment时长也很简单,把原来onResume和onPause中的方法搬到这两个方法里即可。
判断Fragment可见的充分条件
我们先思考Fragment不可见都有几种情况:
- Activity 进入后台,Fragment#onPause回调。
- ViewPager中被滑出屏幕之外的Fragment
我们可以通过getUserVisibleHint()
方法判断Fragment是否在屏幕之中。 - Activity执行
getSupportFragmentManager().beginTransaction().hide(fragment)
的Fragment。
这时Fragment#onHiddenChanged(boolean hidden)
方法会执行。
除去上面三种情况,Fragment对于用户就是可见的了。
我们在 BetterLifecycleFragment中添加isFragmentVisible()
方法用来判断Fragment是否可见。
public class BetterLifecycleFragment extends Fragment {
private boolean isResuming = false;
private boolean hidden = false;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hidden = false;
}
@Override
public void onResume() {
super.onResume();
isResuming = true;
}
@Override
public void onPause() {
super.onPause();
isResuming = false;
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
this.hidden = hidden;
}
/**
* Fragment是否可见
* @return
*/
public boolean isFragmentVisible() {
if (isResuming()
&& getUserVisibleHint()
&& !hidden) {
return true;
}
return false;
}
/**
* Fragment 是否在前台。
* @return
*/
private boolean isResuming() {
return isResuming;
}
}
注意这里我们并没有使用系统的 isResumed()以及isHiddlen();
为什么呢?不妨自己在onPause以及onHiddenChanged(boolean hidden)
里面打印一下这两个方法就知道了。
完整实现
上述已经说过,Fragment不可见有三种情况。同理Fragment可见也和这三种情况有关。
Fragment可见状态改变的三种情况:
- Fragment进入前台或者进入后台:
对应 onResume 和 onPause - ViewPager 滑动:
Fragment 的setUserVisibleHint(boolean isVisibleToUser)
触发。(事实上这个方法根本不会改变Fragment是否可见,只是告诉我们Fragment是否在屏幕之中。) -
FragmentManager
调用了show或者hide:
Fragment 的onHiddenChanged(boolean hidden)
触发。
我们只需在Fragment中可能改变其可见状态的方法中调用我们的 isFragmentVisible()
,然后和上次Fragment的可见状态做个比较,即可得到Fragment可见以及不可见的回调。
完整代码如下:
public class BetterLifecycleFragment extends Fragment {
private boolean isLastVisible = false;
private boolean hidden = false;
private boolean isFirst = true;
private boolean isResuming = false;
private boolean isViewDestroyed = false;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isLastVisible = false;
hidden = false;
isFirst = true;
isViewDestroyed = false;
}
@Override
public void onResume() {
super.onResume();
isResuming = true;
tryToChangeVisibility(true);
}
@Override
public void onPause() {
super.onPause();
isResuming = false;
tryToChangeVisibility(false);
}
@Override
public void onDestroyView() {
super.onDestroyView();
isViewDestroyed = true;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
setUserVisibleHintClient(isVisibleToUser);
}
private void setUserVisibleHintClient(boolean isVisibleToUser) {
tryToChangeVisibility(isVisibleToUser);
if (isAdded()) {
// 当Fragment不可见时,其子Fragment也是不可见的。
// 因此当Fragment可见状态改变了需要通知子Fragment当前可见状态改变了。
List fragments = getChildFragmentManager().getFragments();
if (fragments != null) {
for (Fragment fragment : fragments) {
if (fragment instanceof BetterLifecycleFragment) {
BetterLifecycleFragment f
= (BetterLifecycleFragment) fragment;
f.setUserVisibleHintClient(isVisibleToUser);
}
}
}
}
}
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
onHiddenChangedClient(hidden);
}
public void onHiddenChangedClient(boolean hidden) {
this.hidden = hidden;
tryToChangeVisibility(!hidden);
if (isAdded()) {
List fragments = getChildFragmentManager().getFragments();
if (fragments != null) {
for (Fragment fragment : fragments) {
if (fragment instanceof BetterLifecycleFragment) {
BetterLifecycleFragment f
= (BetterLifecycleFragment) fragment;
f.setUserVisibleHintClient(isVisibleToUser);
}
}
}
}
}
private void tryToChangeVisibility(boolean tryToShow) {
// 上次可见
if (isLastVisible) {
if (tryToShow) {
return;
}
if (!isFragmentVisible()) {
onFragmentPause();
isLastVisible = false;
}
// 上次不可见
} else {
boolean tryToHide = !tryToShow;
if (tryToHide) {
return;
}
if (isFragmentVisible()) {
onFragmentResume(isFirst, isViewDestroyed);
isLastVisible = true;
isFirst = false;
}
}
}
/**
* Fragment是否可见
*
* @return
*/
public boolean isFragmentVisible() {
if (isResuming()
&& getUserVisibleHint()
&& !hidden) {
return true;
}
return false;
}
/**
* Fragment 是否在前台。
*
* @return
*/
private boolean isResuming() {
return isResuming;
}
/**
* Fragment 可见时回调
*
* @param isFirst 是否是第一次显示
* @param isViewDestroyed Fragment中的View是否被回收过。
* 存在这种情况:Fragment 的 View 被回收,但是Fragment实例仍在。
*/
protected void onFragmentResume(boolean isFirst, boolean isViewDestroyed) {
}
/**
* Fragment 不可见时回调
*/
protected void onFragmentPause() {
}
}
值得注意:
- 如果Fragment存在嵌套,那么父Fragment的可见状态将影响到子Fragment的可见状态。所以父Fragment在可见状态改变时要通知子Fragment可见状态发生改变了。
- 考虑一种情况,Fragment的View被回收,但是Fragment实例仍在,这种在ViewPager中是存在的。所以在onFragmentResume方法中我加入了一个isViewDestroyed表示Fragment的View是否被收回过。
懒加载实现如下:
@Override
protected void onFragmentResume(boolean isFirst, boolean isViewDestroy) {
if(isFirst){
loadData();
} else if(isViewDestroy){
// 为View设置数据。当然也可以在onCreateView或者onActivityCreate中设置数据。
setViews();
}
}
友盟统计Fragment时长只需把原来onResume和onPuase中的方法copy到onFragmentResume及onFragmentPause方法中即可。
点我下载源码