監聽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);
        }
    }
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章