【Android Fragment】解決ViewPager巢狀時Fragment的mUserVisibleHint屬性不同步的問題

panpf發表於2019-04-15

2017-1-11日更新:setUserVisibleHint中過濾父Fragment未顯示的情況

上一篇【Android】友盟統計Fragment頁面顯示隱藏的完美解決方案 我們講了通過Fragment的mUserVisibleHint屬性可以準確的監聽Fragment在ViewPager中的顯示與隱藏

現在新的問題又來了,當ViewPager巢狀ViewPager的時候子ViewPager中Fragment的mUserVisibleHint屬性卻不會同其父Fragment的mUserVisibleHint同步,這樣一來子ViewPager中Fragment的狀態的統計就不準確了

舉個栗子,現有如下結構:

ViewPagerParent
    Fragment1
        ViewPagerChild1
            Fragment11
            Fragment12
    Fragment2
        ViewPagerChild2
            Fragment21
            Fragment22
複製程式碼

初始化完成後的狀態是

ViewPagerParent
    Fragment1.mUserVisibleHint=true
        ViewPagerChild1
            Fragment11.mUserVisibleHint=true
            Fragment12.mUserVisibleHint=false
    Fragment2.mUserVisibleHint=false
        ViewPagerChild2
            Fragment21.mUserVisibleHint=true
            Fragment22.mUserVisibleHint=false
複製程式碼

可以明確的看出來這時候Fragment11和Fragment21的mUserVisibleHint屬性都是true,如果我們通過上一篇中講述的方法用getUserVisibleHint()&&isResume()來記錄頁面顯示日誌的話一下子會記錄Fragment11和Fragment21都顯示了,

可這時候我們能看到的只有Fragment11,所以我們不希望Fragment21的mUserVisibleHint也是true,可ViewPager並未提供解決此問題的方法,得靠自己來解決

接下來滑動到Fragment2,這時候的狀態是

ViewPagerParent
    Fragment1.mUserVisibleHint=false
        ViewPagerChild1
            Fragment11.mUserVisibleHint=true
            Fragment12.mUserVisibleHint=false

    Fragment2.mUserVisibleHint=true
        ViewPagerChild2
            Fragment21.mUserVisibleHint=true
            Fragment22.mUserVisibleHint=false
複製程式碼

我們可以看到Fragment1的mUserVisibleHint屬性已經是false了,可是其子Framgnet11依然還是true,並不會跟父Fragment1同步,這時候我們開啟新的Activity再回來重走onResume()方法的時候所有Fragment都會重走onResume()方法,那麼這時候所有mUserVisibleHint狀態是true的Fragment都會記錄一次顯示事件

####我們先來解決初始化時候讓Fragment21.mUserVisibleHint為false 解決思路就是在子Fragment onAttach的時候檢查其父Fragment的mUserVisibleHint屬性的狀態,如果是false就強制將當前子Fragment的mUserVisibleHint屬性設定為false並設定一個恢復標記(因為接下來還需要恢復) 然後在父Fragment setUserVisibleHint為true的時候檢查其所有子Fragment,有恢復標記的子Fragment,設定其mUserVisibleHint屬性為true,看程式碼:

public class BaseFragment extends Fragment{
    private boolean waitingShowToUser;

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // 如果自己是顯示狀態,但父Fragment卻是隱藏狀態,就把自己也改為隱藏狀態,並且設定一個等待顯示標記
        if(getUserVisibleHint()){
            Fragment parentFragment = getParentFragment();
            if(parentFragment != null && !parentFragment.getUserVisibleHint()){
                waitingShowToUser = true;
                super.setUserVisibleHint(false);
            }
        }
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        if(getActivity() != null) {
            List<Fragment> childFragmentList = getChildFragmentManager().getFragments();
            if (isVisibleToUser) {
                // 將所有正等待顯示的子Fragment設定為顯示狀態,並取消等待顯示標記
                if (childFragmentList != null && childFragmentList.size() > 0) {
                    for (Fragment childFragment : childFragmentList) {
                        if (childFragment instanceof BaseFragment) {
                            BaseFragment childBaseFragment = (BaseFragment) childFragment;
                            if (childBaseFragment.isWaitingShowToUser()) {
                                childBaseFragment.setWaitingShowToUser(false);
                                childFragment.setUserVisibleHint(true);
                            }
                        }
                    }
                }
            }
        }
    }

    public boolean isWaitingShowToUser() {
        return waitingShowToUser;
    }

    public void setWaitingShowToUser(boolean waitingShowToUser) {
        this.waitingShowToUser = waitingShowToUser;
    }
}
複製程式碼

####接下來解決父ViewPager切換的時候同步其子Fragment的mUserVisibleHint屬性 思路就是在父Fragment setUserVisibleHint為false的時候將其所有mUserVisibleHint為true的子Fragment都強制改為false,然後設定一個恢復標記,然後在setUserVisibleHint為true的時候再恢復,看程式碼:

public class BaseFragment extends Fragment{
    private boolean waitingShowToUser;

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // 如果自己是顯示狀態,但父Fragment卻是隱藏狀態,就把自己也改為隱藏狀態,並且設定一個等待顯示標記
        if(getUserVisibleHint()){
            Fragment parentFragment = getParentFragment();
            if(parentFragment != null && !parentFragment.getUserVisibleHint()){
                waitingShowToUser = true;
                super.setUserVisibleHint(false);
            }
        }
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        // 父Fragment還沒顯示,你著什麼急
        if (isVisibleToUser) {
            Fragment parentFragment = getParentFragment();
            if (parentFragment != null && !parentFragment.getUserVisibleHint()) {
                waitingShowToUser = true;
                super.setUserVisibleHint(false);
                return;
            }
        }

        if(getActivity() != null) {
            List<Fragment> childFragmentList = getChildFragmentManager().getFragments();
            if (isVisibleToUser) {
                // 將所有正等待顯示的子Fragment設定為顯示狀態,並取消等待顯示標記
                if (childFragmentList != null && childFragmentList.size() > 0) {
                    for (Fragment childFragment : childFragmentList) {
                        if (childFragment instanceof BaseFragment) {
                            BaseFragment childBaseFragment = (BaseFragment) childFragment;
                            if (childBaseFragment.isWaitingShowToUser()) {
                                childBaseFragment.setWaitingShowToUser(false);
                                childFragment.setUserVisibleHint(true);
                            }
                        }
                    }
                }
            } else {
                // 將所有正在顯示的子Fragment設定為隱藏狀態,並設定一個等待顯示標記
                if (childFragmentList != null && childFragmentList.size() > 0) {
                    for (Fragment childFragment : childFragmentList) {
                        if (childFragment instanceof BaseFragment) {
                            BaseFragment childBaseFragment = (BaseFragment) childFragment;
                            if (childFragment.getUserVisibleHint()) {
                                childBaseFragment.setWaitingShowToUser(true);
                                childFragment.setUserVisibleHint(false);
                            }
                        }
                    }
                }
            }
        }
    }

    public boolean isWaitingShowToUser() {
        return waitingShowToUser;
    }

    public void setWaitingShowToUser(boolean waitingShowToUser) {
        this.waitingShowToUser = waitingShowToUser;
    }
}
複製程式碼

####最後我們結合上一篇文章的需求將對Fragment.mUserVisibleHint屬性的管理封裝成一個FragmentUserVisibleController.java,如下:

/**
 * Fragment的mUserVisibleHint屬性控制器,用於準確的監聽Fragment是否對使用者可見
 * <br>
 * <br>mUserVisibleHint屬性有什麼用?
 * <br>* 使用ViewPager時我們可以通過Fragment的getUserVisibleHint()&&isResume()方法來判斷使用者是否能夠看見某個Fragment
 * <br>* 利用這個特性我們可以更精確的統計頁面的顯示事件和標準化頁面初始化流程(真正對使用者可見的時候才去請求資料)
 * <br>
 * <br>解決BUG
 * <br>* FragmentUserVisibleController還專門解決了在Fragment或ViewPager巢狀ViewPager時子Fragment的mUserVisibleHint屬性與父Fragment的mUserVisibleHint屬性不同步的問題
 * <br>* 例如外面的Fragment的mUserVisibleHint屬性變化時,其包含的ViewPager中的Fragment的mUserVisibleHint屬性並不會隨著改變,這是ViewPager的BUG
 * <br>
 * <br>使用方式(假設你的基類Fragment是MyFragment):
 * <br>1. 在你的MyFragment的建構函式中New一個FragmentUserVisibleController(一定要在建構函式中new)
 * <br>2. 重寫Fragment的onActivityCreated()、onResume()、onPause()、setUserVisibleHint(boolean)方法,分別呼叫FragmentUserVisibleController的activityCreated()、resume()、pause()、setUserVisibleHint(boolean)方法
 * <br>3. 實現FragmentUserVisibleController.UserVisibleCallback介面並實現以下方法
 * <br>&nbsp&nbsp&nbsp&nbsp* void setWaitingShowToUser(boolean):直接呼叫FragmentUserVisibleController的setWaitingShowToUser(boolean)即可
 * <br>&nbsp&nbsp&nbsp&nbsp* void isWaitingShowToUser():直接呼叫FragmentUserVisibleController的isWaitingShowToUser()即可
 * <br>&nbsp&nbsp&nbsp&nbsp* void callSuperSetUserVisibleHint(boolean):呼叫父Fragment的setUserVisibleHint(boolean)方法即可
 * <br>&nbsp&nbsp&nbsp&nbsp* void onVisibleToUserChanged(boolean, boolean):當Fragment對使用者可見或不可見的就會回撥此方法,你可以在這個方法裡記錄頁面顯示日誌或初始化頁面
 * <br>&nbsp&nbsp&nbsp&nbsp* boolean isVisibleToUser():判斷當前Fragment是否對使用者可見,直接呼叫FragmentUserVisibleController的isVisibleToUser()即可
 */
@SuppressLint("LongLogTag")
public class FragmentUserVisibleController {
    private static final String TAG = "FragmentUserVisibleController";
    public static boolean DEBUG = false;
    @SuppressWarnings("FieldCanBeLocal")
    private String fragmentName;
    private boolean waitingShowToUser;
    private Fragment fragment;
    private UserVisibleCallback userVisibleCallback;
    private List<OnUserVisibleListener> userVisibleListenerList;

    public FragmentUserVisibleController(Fragment fragment, UserVisibleCallback userVisibleCallback) {
        this.fragment = fragment;
        this.userVisibleCallback = userVisibleCallback;
        //noinspection ConstantConditions
        this.fragmentName = DEBUG ? fragment.getClass().getSimpleName() : null;
    }

    public void activityCreated() {
        if (DEBUG) {
            Log.d(TAG, fragmentName + ": activityCreated, userVisibleHint=" + fragment.getUserVisibleHint());
        }
        if (fragment.getUserVisibleHint()) {
            Fragment parentFragment = fragment.getParentFragment();
            if (parentFragment != null && !parentFragment.getUserVisibleHint()) {
                if (DEBUG) {
                    Log.d(TAG, fragmentName + ": activityCreated, parent " + parentFragment.getClass().getSimpleName() + " is hidden, therefore hidden self");
                }
                userVisibleCallback.setWaitingShowToUser(true);
                userVisibleCallback.callSuperSetUserVisibleHint(false);
            }
        }
    }

    public void resume() {
        if (DEBUG) {
            Log.d(TAG, fragmentName + ": resume, userVisibleHint=" + fragment.getUserVisibleHint());
        }
        if (fragment.getUserVisibleHint()) {
            userVisibleCallback.onVisibleToUserChanged(true, true);
            callbackListener(true, true);
            if (DEBUG) {
                Log.i(TAG, fragmentName + ": visibleToUser on resume");
            }
        }
    }

    public void pause() {
        if (DEBUG) {
            Log.d(TAG, fragmentName + ": pause, userVisibleHint=" + fragment.getUserVisibleHint());
        }
        if (fragment.getUserVisibleHint()) {
            userVisibleCallback.onVisibleToUserChanged(false, true);
            callbackListener(false, true);
            if (DEBUG) {
                Log.w(TAG, fragmentName + ": hiddenToUser on pause");
            }
        }
    }

    public void setUserVisibleHint(boolean isVisibleToUser) {
        Fragment parentFragment = fragment.getParentFragment();
        if (DEBUG) {
            String parent;
            if (parentFragment != null) {
                parent = "parent " + parentFragment.getClass().getSimpleName() + " userVisibleHint=" + parentFragment.getUserVisibleHint();
            } else {
                parent = "parent is null";
            }
            Log.d(TAG, fragmentName + ": setUserVisibleHint, userVisibleHint=" + isVisibleToUser + ", " + (fragment.isResumed() ? "resume" : "pause") + ", " + parent);
        }

        // 父Fragment還沒顯示,你著什麼急
        if (isVisibleToUser) {
            if (parentFragment != null && !parentFragment.getUserVisibleHint()) {
                if (DEBUG) {
                    Log.d(TAG, fragmentName + ": setUserVisibleHint, parent " + parentFragment.getClass().getSimpleName() + " is hidden, therefore hidden self");
                }
                userVisibleCallback.setWaitingShowToUser(true);
                userVisibleCallback.callSuperSetUserVisibleHint(false);
                return;
            }
        }

        if (fragment.isResumed()) {
            userVisibleCallback.onVisibleToUserChanged(isVisibleToUser, false);
            callbackListener(isVisibleToUser, false);
            if (DEBUG) {
                if (isVisibleToUser) {
                    Log.i(TAG, fragmentName + ": visibleToUser on setUserVisibleHint");
                } else {
                    Log.w(TAG, fragmentName + ": hiddenToUser on setUserVisibleHint");
                }
            }
        }

        if (fragment.getActivity() != null) {
            List<Fragment> childFragmentList = fragment.getChildFragmentManager().getFragments();
            if (isVisibleToUser) {
                // 顯示待顯示的子Fragment
                if (childFragmentList != null && childFragmentList.size() > 0) {
                    for (Fragment childFragment : childFragmentList) {
                        if (childFragment instanceof UserVisibleCallback) {
                            UserVisibleCallback userVisibleCallback = (UserVisibleCallback) childFragment;
                            if (userVisibleCallback.isWaitingShowToUser()) {
                                if (DEBUG) {
                                    Log.d(TAG, fragmentName + ": setUserVisibleHint, show child " + childFragment.getClass().getSimpleName());
                                }
                                userVisibleCallback.setWaitingShowToUser(false);
                                childFragment.setUserVisibleHint(true);
                            }
                        }
                    }
                }
            } else {
                // 隱藏正在顯示的子Fragment
                if (childFragmentList != null && childFragmentList.size() > 0) {
                    for (Fragment childFragment : childFragmentList) {
                        if (childFragment instanceof UserVisibleCallback) {
                            UserVisibleCallback userVisibleCallback = (UserVisibleCallback) childFragment;
                            if (childFragment.getUserVisibleHint()) {
                                if (DEBUG) {
                                    Log.d(TAG, fragmentName + ": setUserVisibleHint, hidden child " + childFragment.getClass().getSimpleName());
                                }
                                userVisibleCallback.setWaitingShowToUser(true);
                                childFragment.setUserVisibleHint(false);
                            }
                        }
                    }
                }
            }
        }
    }

    private void callbackListener(boolean isVisibleToUser, boolean invokeInResumeOrPause) {
        if (userVisibleListenerList != null && userVisibleListenerList.size() > 0) {
            for (OnUserVisibleListener listener : userVisibleListenerList) {
                listener.onVisibleToUserChanged(isVisibleToUser, invokeInResumeOrPause);
            }
        }
    }

    /**
     * 當前Fragment是否對使用者可見
     */
    @SuppressWarnings("unused")
    public boolean isVisibleToUser() {
        return fragment.isResumed() && fragment.getUserVisibleHint();
    }

    public boolean isWaitingShowToUser() {
        return waitingShowToUser;
    }

    public void setWaitingShowToUser(boolean waitingShowToUser) {
        this.waitingShowToUser = waitingShowToUser;
    }

    public void addOnUserVisibleListener(OnUserVisibleListener listener) {
        if (listener != null) {
            if (userVisibleListenerList == null) {
                userVisibleListenerList = new LinkedList<OnUserVisibleListener>();
            }
            userVisibleListenerList.add(listener);
        }
    }

    public void removeOnUserVisibleListener(OnUserVisibleListener listener) {
        if (listener != null && userVisibleListenerList != null) {
            userVisibleListenerList.remove(listener);
        }
    }

    public interface UserVisibleCallback {
        boolean isWaitingShowToUser();

        void setWaitingShowToUser(boolean waitingShowToUser);

        boolean isVisibleToUser();

        void callSuperSetUserVisibleHint(boolean isVisibleToUser);

        void onVisibleToUserChanged(boolean isVisibleToUser, boolean invokeInResumeOrPause);
    }

    public interface OnUserVisibleListener {
        void onVisibleToUserChanged(boolean isVisibleToUser, boolean invokeInResumeOrPause);
    }
}
複製程式碼

使用起來非常簡單,可快速整合到你的Fragment中,:

1. 在你的基類Fragment的建構函式中New一個FragmentUserVisibleController(一定要在建構函式中new) 2. 重寫Fragment的onActivityCreated()、onResume()、onPause()、setUserVisibleHint(boolean)方法,分別呼叫FragmentUserVisibleController的activityCreated()、resume()、pause()、setUserVisibleHint(boolean)方法 3. 實現FragmentUserVisibleController.UserVisibleCallback介面並實現以下方法 void setWaitingShowToUser(boolean):直接呼叫FragmentUserVisibleController的setWaitingShowToUser(boolean)即可 void isWaitingShowToUser():直接呼叫FragmentUserVisibleController的isWaitingShowToUser()即可 void callSuperSetUserVisibleHint(boolean):呼叫父Fragment的setUserVisibleHint(boolean)方法即可 void onVisibleToUserChanged(boolean, boolean):當Fragment對使用者可見或不可見的就會回撥此方法,你可以在這個方法裡記錄頁面顯示日誌或初始化頁面 boolean isVisibleToUser():判斷當前Fragment是否對使用者可見,直接呼叫FragmentUserVisibleController的isVisibleToUser()即可

如下所示:

public class MyFragment extends Fragment implements FragmentUserVisibleController.UserVisibleCallback{
    private FragmentUserVisibleController userVisibleController;

    public MyFragment() {
        userVisibleController = new FragmentUserVisibleController(this, this);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        userVisibleController.activityCreated();
    }

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

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

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        userVisibleController.setUserVisibleHint(isVisibleToUser);
    }

    @Override
    public void setWaitingShowToUser(boolean waitingShowToUser) {
        userVisibleController.setWaitingShowToUser(waitingShowToUser);
    }

    @Override
    public boolean isWaitingShowToUser() {
        return userVisibleController.isWaitingShowToUser();
    }

    @Override
    public boolean isVisibleToUser() {
        return userVisibleController.isVisibleToUser();
    }

    @Override
    public void callSuperSetUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
    }

    @Override
    public void onVisibleToUserChanged(boolean isVisibleToUser, boolean invokeInResumeOrPause) {

    }
}
複製程式碼

相關文章