Android進階學習(11)-- ViewPager嵌套Fragment懶加載處理

需要懶加載的情況分析

  1. fragment可見:
    分爲兩種(1)第一次可見(2)來回切換後可見
    第一次可見:意味着加載的數據更多,網絡請求更多。
    來回切換後可見:可能在第一次網絡請求後將數據緩存在本地,那麼只需要讀取緩存即可;
  2. fragment不可見:
    當fragment不可見時,需要中斷網絡請求(或者輪播圖Banner等)

綜合以上兩個可見和不可見狀態,能夠感知的生命週期只有onResume和onPasue,外加一個setUserVisibleHint方法,那麼懶加載也就是在這三個方法中分發正確的事件,可以將第一次加載,懶加載,中斷,這三個操作抽出成爲方法,由子類實現,父類負責分發。

結合生命週期的坑點

上面的兩種情況分析,看上去非常的簡單,實際上懶加載也就是完成了以上的工作,但是,實際代碼中,會有坑,onResume和onPause。
先說onResume需要考慮的問題:
假設,現在有三個tab一個viewpager,每個tab對應着一個fragment。當tab1切換到tab2時,由於viewpager中的setOffscreenPageLimit默認值爲1,也就意味着tab3此時會走onResume的生命週期;
再比如:Activity1中有多個fragment,此時有Activity1跳轉到Activity2,那麼Activity1中會緩存多個Fragment,當由Activity2返回Activity1時,會走fragment的onResume生命週期;
那麼onResume要做如下處理:
僞代碼:

public void onResume() {
        super.onResume();
        //這個if是爲了解決tab來回切換的問題,當切換到tab2時,tab3已經執行onResume,但是view還沒創建
        //所以需要增加一層判斷,當第一次可見時,再去分發事件
        if(如果第一次可見){ 
        	//由於activity切換時,fragment不會走setUserVisibleHint方法,所以也需要增加一層判斷
            if (沒有hidden && 當前狀態不可見 && getUserVisibleHint()){
                //分發可見事件
            }
        }
    }

解決完onResume,繼續解決onPause
onPause不需要onResume裏的第一層判斷,因爲只要執行onPause那說明已經執行過了onCreate以及onResume。同樣,當Activity1跳轉到Activity2時,1裏面的所有fragment都會執行onPause,那麼只需要處理由可見變爲不可見狀態的fragment,由可見變爲不可見時,需要分發事件,處理中斷操作。
僞代碼:

    public void onPause() {
        super.onPause();
        //這裏也就是說 當前可見的Fragment 需要分發中斷事件
        if (當前可見 && getUserVisibleHint()){
            //分發不可見事件
        }
    }

Fragment嵌套情況處理

bug出現情況,假設當前有三個tab,每個tab對應一個fragment,tab2對應的fragment裏嵌套了一層viewpager,當tab1加載時,tab2對應的fragment裏面嵌套的viewpager裏的第一個fragment也會執行onResume。這個情況是非常容易被忽略掉的,處理的邏輯很簡單,在分發事件時,首先判斷父Fragment是否可見,只有可見情況下才會繼續分發。

完整代碼

使用時,對應的Fragment繼承該父類,子類主動實現firstLoad、lazyLoad、interrupt 三個方法即可。如果有嵌套,嵌套的Fragment也繼承該父類即可

public abstract class LazyFragment extends Fragment {

    protected View mRootView = null; //view複用
    protected boolean isCreateView = false; //是否創建
    protected boolean currentVisibleState = false; //當前可見狀態
    protected boolean isFirstVisible = true; //是否第一次可見

    protected abstract int setLayoutId();
	
	//初始化view
    protected abstract void initView(View view);

	//第一次加載 留給子類實現
    protected void firstLoad(){
    }
	
	//切換之後的懶加載 留給子類實現
    protected void lazyLoad() {
    }
	
	//切換後中斷加載 留給子類實現
    protected void interrupt() {
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    	//父view複用 
        if (mRootView == null) {
            mRootView = inflater.inflate(setLayoutId(), container, false);
        }
        initView(mRootView);
        //已經創建view
        isCreateView = true;
        //第一次加載
        if (!isHidden() && getUserVisibleHint()) {
            dispatchLazyLoadOrInterrupt(true);
        }
        return mRootView;
    }

    @Override
    public void onResume() {
        super.onResume();
        /**
         * 已經加載過的 fragment 會遇到的情況
         *     fragment 所在的 activity 跳轉到其他 activity
         *     在 activity 返回時 需要加載
         *     只加載可見 fragment
         */
        if(!isFirstVisible){
            if (!isHidden() && !currentVisibleState && getUserVisibleHint()){
                dispatchLazyLoadOrInterrupt(true);
            }
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        //是否需要分發中斷事件
        if (currentVisibleState && getUserVisibleHint()){
            dispatchLazyLoadOrInterrupt(false);
        }
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isCreateView) { // createview 之後纔可以加載數據
            if (!currentVisibleState && isVisibleToUser) { //不可見 -> 可見
                dispatchLazyLoadOrInterrupt(true);
            } else if (currentVisibleState && !isVisibleToUser) { // 可見 -> 不可見
                dispatchLazyLoadOrInterrupt(false);
            }
        }
    }

    /**
     * 處理fragment切換時 懶加載 和 中斷
     * 可見 -> 不可見 = 中斷操作,網絡請求等
     * 不可見 -> 可見 = 懶加載
     * @param isVisible
     */
    private void dispatchLazyLoadOrInterrupt(boolean isVisible) {
        // ParentFragment不可見 則不處理
        // 處理fragment裏嵌套fragment的情況
        if (isVisible && isParentInVisible()) {
            return;
        }
        if (currentVisibleState == isVisible) {
            return;
        }
        // save當前可見性
        currentVisibleState = isVisible;
        if (isVisible) {
            /**
             * fragment 第一次加載
             * 主要處理這種情況:
             *     fragment第一次加載網絡請求的數據緩存到本地,之後加載去讀取本地
             *     那麼,第一次網絡請求通過 firstLoad,之後在 lazyLoad 中讀取本都緩存
             */
            if (isFirstVisible){
                isFirstVisible = false;
                firstLoad();
            }
            lazyLoad();
            dispatchChildLazyLoadOrInterrupt(isVisible);
        } else {
            interrupt();
            dispatchChildLazyLoadOrInterrupt(isVisible);
        }
    }

    /**
     * 當 fragment 中嵌套 fragment 時要處理 可見的childfragment
     * @param isVisible
     */
    public void dispatchChildLazyLoadOrInterrupt(boolean isVisible){
        FragmentManager fm = getChildFragmentManager();
        List<Fragment> fragmentList = fm.getFragments();
        for (Fragment item : fragmentList){
            if (item instanceof LazyFragment && !isHidden() && item.getUserVisibleHint()){
                ((LazyFragment) item).dispatchLazyLoadOrInterrupt(isVisible);
            }
        }
    }

    /**
     * 當 fragment 通過 FragmentTransaction 的 show/hide 方法
     * 改變 Fragment 可見性時,在這裏處理調度邏輯
     * @param hidden
     */
    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if (hidden){
            dispatchLazyLoadOrInterrupt(false);
        }else {
            dispatchLazyLoadOrInterrupt(true);
        }
    }

    /**
     * ParentFragment 是不是不可見
     *
     * @return true -> 不可見 / false -> 可見
     */
    private boolean isParentInVisible() {
        Fragment parent = getParentFragment();
        if (parent instanceof LazyFragment) {
            LazyFragment temp = (LazyFragment) parent;
            return !temp.getCurrentVisibleState();
        }
        return false;
    }

    /**
     * 獲取 fragment 當前的可見性
     * @return
     */
    private boolean getCurrentVisibleState() {
        return currentVisibleState;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章