1. 程式人生 > >【轉】Fragment對用戶可見的判斷實踐,親測有效

【轉】Fragment對用戶可見的判斷實踐,親測有效

有一個 跳轉 orm from defaults 才會 over als class

概述

相信很多使用過 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();
        }
    }

還是用一些場景來說明一下吧。

  1. 如果一個Fragment跳轉到另一個 Activity,回來時會調用 Fragment 的 onResume 方法,這時,由於不在 ViewPager 中,getUserVisibleHint 默認是返回 true 的,那 Fragment 是否可見就依賴於 isHidden 方法了,如果跳轉時是可見,那 isHidden 就是 false,執行 onVisible 回調,如果 跳轉時不可見,那 isHidden 就是 true,那麽就不會回調 onVisible了。
  2. 如果是在 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 狀態了,所以用了一個新標識去表明不在屏幕內,標識為不可見。所以要結合判斷,不能只判斷其中一個。


作者:dylanhuang88
鏈接:https://www.jianshu.com/p/399d0f43f34e
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請註明出處。

【轉】Fragment對用戶可見的判斷實踐,親測有效