监听Fragment对用户是否真实可见

需求: 监听Fragment 对用户真实可见或不可见

网上有很多方法,专门整理了一下。
 

先介绍四个关于fragment 对用户是否可见的方法

1.onHiddenChanged 方法

备注: 当fragment 被执行show() hide()方法时会调用该方法。 同时页面和前台后切换是不会执行该方法。


@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
        if(hidden){
                //TODO now visible to user
        } else {
                //TODO now invisible to user
        }
}

2. setUserVisibleHint(boolean isVisibleToUser) 方法。 

备注: 该方法是Fragment本身执行了该方法。 同时页面和前台后切换是不会执行该方法。

使用场景: 在 ViewPager + Fragment 中,会使用 FragmentStatePagerAdapter 或 FragmentPagerAdapter。。 在页面切换时这两个adapter 会调用 setUserVisibleHint 方法, 所以我们在使用viewpager切换页面时, Fragment重写该方法会监听到该方法的执行。

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser) {
        // 
    } else {
        // 
    }
}

3.  onResume() 和onPause() 方法。 

备注: 可以使用 isResumed() 方法来判断是否执行onResume()。 请注意,在回调onResueme()方法时,它的值已经为true.

使用场景: 在activity 切换, 前后台切换时会调用该方法。 

 

4. 工具类 判断View是否在屏幕中显示。 isCover()

备注: 该方法可以判断一个view是否显示在屏幕中。但必须要注意的是,在viewpager切换时,以及fragment 执行show() hide()方法时它返回的值并不是我们想要的值。 因为该方法判断的是view完全不在屏幕上,例如在viewpager在切换页面时,即将隐藏的fragment是显示在屏幕上的,而即将显示的fragment却还是隐藏的;另外,在前后台切换时, 该方法判断刚才显示在用户面前的view还是为显示在屏幕中。。。这个结果是有问题的啊。

该方法适用于view已经显示在屏幕上或已经完全不显示在屏幕上。

在view并未在屏幕上展示时, 是不可见的. 返回是true. 

/**
* view是否不可见
*
* @param view
* @return
*/
public static boolean isCover(View view) {
    boolean cover = false;
    if (view != null) {
        Rect rect = new Rect();
        cover = view.getGlobalVisibleRect(rect);
        if (cover) {
            if (rect.width() >= view.getMeasuredWidth() && rect.height() >= view.getMeasuredHeight()) {
                return !cover;
            }
        }
    }
    return true;
}

 

Fragment 对用户是否可见?

请根据自己实际使用场景来判断。

场景一: activity + Fragment . 

通过show() hide() 来控制Fragment 的显示与隐藏。

解决文案:isResumed() + 重写onHiddenChanged方法

    是否对用户可见 = isResumed() && mIsVisibleToUser ;

    public void onHiddenChanged(boolean hidden) {//fragment show hide api调用时会调用该方法
        super.onHiddenChanged(hidden);
        mIsVisibleToUser = !hidden;
        notifyUserVisible();
    }

 

场景二: viewpager + fragment

解决文案:isResumed() + 重写 setUserVisibleHint 方法

   
    public void onHiddenChanged(boolean hidden) {//fragment show hide api调用时会调用该方法
        super.onHiddenChanged(hidden);
        mIsVisibleToUser = !hidden;
        notifyUserVisible();
    }

 @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {//viewpager切换页面时会调用该方法
        super.setUserVisibleHint(isVisibleToUser);
        this.mIsVisibleToUser = isVisibleToUser;
        notifyUserVisible();
    }

    @Override
    public void onResume() {
        super.onResume();
        notifyUserVisible();
    }

    @Override
    public void onPause() {
        super.onPause();
        notifyUserVisible();
    }

    private void notifyUserVisible() {
            isUserVisible();
    
    }

    public boolean isUserVisible() {
        return isResumed() && mIsVisibleToUser;
    }


    /**
     * 对用户可见变化(使用时调用重写该方法
     *
     * @param isVisibleToUser
     */
    public void onUserVisibleChange(boolean isVisibleToUser) {

    }

 

场景三:activity +  viewpager + fragment   多层嵌套

主页activity 加载子Fragment (我们叫它f0), 通过show hide来控制显示与隐藏 ,  f0 里面是viewpager + 3个fragment (f1, f2,f3),  f2 f3  fragment里面是 viewPager + fragment 模式 。 f2里面有三个子fragment (f21 f22 f23), f3 fragment中的Viewpager只有一个fragment f31.

特别注意的地方: 

1. 当父Fragment f0 隐藏与显示(执行show() hide()方法)时, 它的所有子fragment 是不会收到任何方法回调的。 

2. 当f0 fragment中的viewpager v1 切换时,它的子fragment f1  f2 f3  的setUserVisibleHint 方法会执行,二级Fragment f1 f2 f3可以拿到自己是否对用户可见。 但是 f2 f3 fragment中的子Fragment 是不会被执行任何方法的。也就是 f21 f22 f23  f31 这四个实际对用户展示数据的页面是不知道父级Fragment 是否对用户真实可见的。

可能会有同行说,怎么可能会这种操作。。。 实际上是有的。 

我司需求: 

1)在如下界面,子界面切换到哪个, 需要立即刷新。

2)部分子界面在对用户可见时以固定频率定时刷新数据,对用户不可见时停止固定频率刷新数据。

顺便说一下这个界面,有下拉刷新,水平滑动需要切换viewpager页面, banner一个页面时不循环,不能滑动,多页面时循环轮播,水平滑动view滑动时需要与同类型的item实现联动,水平滑动view滑动到头时再滑动可以切换 viewpager,,两层viewpager,子viewpager不能滑动时,则外层的viewpager可以滑动。如果有人说一个页面有同向滚动是不太合理的需求,请问下面这个界面该如何吐槽了。。。 三层水平滑动控件,下拉刷新控件+recyclerview , 固定频率刷新界面

 

言归正传: 如何判断子frgment 对用户是否真实可见?

思路: 父view是否对用户可见, 需要通知子view , 每个界面根据不同的场景重写不同的方法。 

父级f0 fragment 重写 onHiddenChanged  和 onResume() onPause() 方法 .在它本身对用户可见状态发生变化时通知子fragment(f1 f2 f3).

f1  f2 f3    f21 f22 f23  f31 fragment, 重写 setUserVisibleHint  和  onResume() onPause()  方法。 

f1  f2 f3  fragment 在被上层F0 Fragment 中viewpager切换时,通知子fragment  f21  f22 f23  f31 父fragment的变化。

 

写了一个公共的 BaseUserVisibleFragment

使用 方法:

step1 : 所有Fragment继承 BaseUserVisibleFragment 并重写  onUserVisibleChange(boolean isVisibleToUser)  方法。

setp2:  父fragment  f0  f1 f2 f3  在自己的onUserVisibleChange 方法里再调用 setFatherUserVisible 方法,通知它们所有子fragment自已本身是否对用户可见。

  @Override
    public void onUserVisibleChange(boolean isVisibleToUser) {
        super.onUserVisibleChange(isVisibleToUser);
        setFatherUserVisible(fragments, isVisibleToUser);
}

setp3:  在父Fragment 初始化 ,为viewpager 添加子Frament 的时候,需要调用setFatherUserVisible (fragment, false); 

为了适用性,字段 mIsFatherVisibleUser  (父控件是否可见) 默认为true. viewpager + fragment 的模式初始化情况下需要将该字段置为false.

特别注意的一点: step2里面通知子fragment 父view 是否可见,通知的fragment集合是初始化时数据,如果父fragment重建了,可能真实显示的子fragment部分不是该集合中的数据了,会导致不能让真实显示的fragment 知道父控件对用户可见的变化,这是由于FragmentManager有缓存的原因,如果你们的fragment可能会重建, 请再着重再处理一下这方面(activity 或fragment重建时清空fragmentManger中的缓存,重新给viewpager设置新的Fragment集合不失为一个解决文案)。

 


import android.support.v4.app.Fragment;

import com.app.haotougu.mvp.presenters.MVPPresenter;

import java.util.List;

public class BaseUserVisibleFragment<T extends MVPPresenter> extends BaseFragment<T> {

    private boolean mIsVisibleToUser;//负责记录viewpager切换 hide show api调用
    private boolean mIsFatherVisibleUser = true;//父控件是否可见
    private VisibleStauts mLastIsUserVisible = VisibleStauts.UNKOWN;//上一次是否可见

    public enum VisibleStauts {
        UNKOWN, VISIBLE, NO_VISIBLIE
    }

    /**
     * 对用户可见变化(使用时调用重写该方法
     *
     * @param isVisibleToUser
     */
    public void onUserVisibleChange(boolean isVisibleToUser) {

    }

    private void notifyUserVisible() {
        //与上一次状态不同时才调用
        if (mLastIsUserVisible != VisibleStauts.NO_VISIBLIE && !isUserVisible()) {
            mLastIsUserVisible = VisibleStauts.NO_VISIBLIE;
            onUserVisibleChange(isUserVisible());
        } else if (mLastIsUserVisible != VisibleStauts.VISIBLE && isUserVisible()) {
            mLastIsUserVisible = VisibleStauts.VISIBLE;
            onUserVisibleChange(isUserVisible());
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        notifyUserVisible();
    }

    @Override
    public void onPause() {
        super.onPause();
        notifyUserVisible();
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {//viewpager切换页面时会调用该方法
        super.setUserVisibleHint(isVisibleToUser);
        this.mIsVisibleToUser = isVisibleToUser;
        notifyUserVisible();
    }

    public void onHiddenChanged(boolean hidden) {//fragment show hide api调用时会调用该方法
        super.onHiddenChanged(hidden);
        mIsVisibleToUser = !hidden;
        notifyUserVisible();
    }

    /**
     * 该fragment是否对用户可见
     * @return
     */
    public boolean isUserVisible() {
        return isResumed() && mIsFatherVisibleUser && mIsVisibleToUser;
    }

    /**
     * 通知父控件是否对用户可见.
     *
     * @param isFatherVisibleUser
     */
    private void notifyFatherUserVisible(boolean isFatherVisibleUser) {
        this.mIsFatherVisibleUser = isFatherVisibleUser;
        notifyUserVisible();
    }

    /**
     * 设置Fragment的父UI是否可见
     * @param list
     * @param isFatherUserVisible
     */
    public static void setFatherUserVisible(List<Fragment> list , boolean isFatherUserVisible) {
        if (list != null) {
            for (Fragment fragment : list) {
                setFatherUserVisible(fragment,isFatherUserVisible);
            }
        }
    }

    /**
     * 设置Fragment的父UI是否可见
     * @param fragment
     * @param isFatherUserVisible
     */
    public static void setFatherUserVisible(Fragment fragment , boolean isFatherUserVisible) {
        if (fragment != null && fragment instanceof BaseUserVisibleFragment) {
            ((BaseUserVisibleFragment) fragment).notifyFatherUserVisible(isFatherUserVisible);
        }
    }
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值