Fragment View 懶加載

TesI55 7年前發布 | 7K 次閱讀 安卓開發 Android開發 移動開發

這個只是一個簡單的類,具體放在 recyclerview-adapter-hf庫 的 目錄 下,作為了demo的一部分,有需要的可以看一下。

需求

  1. ViewPager在切換時Fragment能正確回調可見性
  2. 頁面跳轉、頁面前后臺切換時能正確回調可見性
  3. 有子集ViewPager嵌套Fragment時能正確回調可見性
  4. 根據Fragment 真實可見性進行view及數據初始化操作

場景限定

這里描述的場景定義在ViewPager中使用Fragment(大多數情況下Fragment在ViewPager中)以及單頁面Fragment

擴展及回調API

/**
 * 視圖初始化回調
 *
 * @param inflater
 * @param container
 * @param savedInstanceState
 * @return
 */
public abstract View onLazyCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);

/**
 * 適用與外層是viewpager(vp_outer) ,viewpager中某個fragment中也用了viewpager(vp_inner)時,用來設置vp_inner
 *
 * @return
 */
protected ViewPager setNestedViewPagerWithNestedFragment() {
    return null;
}

/**
 * fragment可見性變化時回調
 *
 * Fragment 可見時該方法會回調一次, 不可見時保證最少調用一次
 * @param visible
 */
protected void onFragmentVisibilityChanged(boolean visible) {}

/**
 * 是否開啟view的懶加載模式
 *
 * @return
 */
protected boolean isViewLazyLoadEnable() {
    return true;
}

功能實現

實現簡介

  1. ViewPager在切換時內部會自動調用 setUserVisibleHint(...)處理Fragment可見性(也僅限于關聯到自身切換的Fragment)
  2. 非ViewPager切換引起的可見性變化需要用Fragment的生命周期方法 onStart()、onStop() 進行處理(比如:我們希望在頁面跳轉以及頁面前后臺切換時也能張確的進行可見性回調)
  3. ViewPager嵌套時

實現

  1. 加入以下兩個方法作為懶加載模式的配置以及Fragment可見性回調

    /**
      * fragment可見性變化時回調
      *
      * @param visible
      */
     protected void onFragmentVisibilityChanged(boolean visible) {}
    
     /**
      * 是否開啟view的懶加載模式
      *
      * @return
      */
     protected boolean isViewLazyLoadEnable() {
         return true;
     }
  2. 懶加載初始化及通過ViewPager進行Fragment可見性回調處理
    我們都知道Fragment中視圖的創建是在 public View onCreateView(...) 方法中,那么對于視圖懶加載來說視圖的初始化時機需要延后處理,通過結合ViewPager內部調用 public void setUserVisibleHint(...) 在當切換到當前Fragment時在進行視圖初始化操作,這個過程需要注意以下幾點:
    1. 初始創建時 public void setUserVisibleHint(...) 優先執行于 public View onCreateView(...)
    2. 系統只會回調 public View onCreateView(...) 一次,所以對于懶加載需要提前創建一個新的rootView作為根視圖占位
    3. public abstract View onLazyCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); 為新的視圖初始化回調方法,通過實現進行其他初始化操作

該部分只是描述了懶加載初始化以及通過ViewPager的特點進行Fragment可見性的回調處理,代碼如下:

private Bundle mSavedInstanceState;

    /** 標識客戶端是否真正初始化了視圖, 通過調用{@link #lazyCreateView} **/
    private boolean mIsRealViewSetup;

    private View mRootView;

    /** 是否已經調用了初始化view方法 **/
    private boolean mIsCalledOnCreateViewMethod = false;

    @Nullable
    @Override
    final
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (this.isViewLazyLoadEnable() == false || originVisibleOfUserHint) {//不是懶加載
            Log.e("LazyLoadFragment", "onCreateView() -> will call lazyCreateView() ");
            this.mRootView = lazyCreateView(LayoutInflater.from(getContext()), container, mSavedInstanceState);
            this.mIsRealViewSetup = true;
        } else {
            Log.e("LazyLoadFragment", "onCreateView() -> init by FrameLayout ");
            this.mRootView = new FrameLayout(getContext());
        }
        Log.e("LazyLoadFragment", "onCreateView -> " + isViewLazyLoadEnable() + " , >> " + getClass().getSimpleName());
        this.mSavedInstanceState = savedInstanceState;
        this.mIsCalledOnCreateViewMethod = true;
        return mRootView;
    }

    private boolean originVisibleOfUserHint = false;

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        this.originVisibleOfUserHint = getUserVisibleHint();

        Log.e("LazyLoadFragment", "setUserVisibleHint -> " + isVisibleToUser + " , originVisibleOfUserHint: " + originVisibleOfUserHint + " ]]> " + getClass().getSimpleName() + " , rootView: " + mRootView);
        if (this.mRootView == null) {
            return;
        }

        if (this.isViewLazyLoadEnable() && isVisibleToUser && mIsCalledOnCreateViewMethod == true && mIsRealViewSetup == false) {
            Log.e("LazyLoadFragment", "setUserVisibleHint() -> will call lazyCreateView() ");
            ViewGroup rootView = (ViewGroup) mRootView;
            rootView.removeAllViews();
            View contentView = lazyCreateView(LayoutInflater.from(getContext()), rootView, mSavedInstanceState);
            rootView.addView(contentView);
            this.mIsRealViewSetup = true;
        }

        if (this.mIsRealViewSetup) {
            this.onFragmentVisibilityChanged(isVisibleToUser, false);
        }
    }
    private View lazyCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.e("LazyLoadFragment", "lazyCreateView -> [-" + getClass().getSimpleName() + "-]");
        return this.onLazyCreateView(inflater, container, savedInstanceState);
    }

    public abstract View onLazyCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);


    /** 記錄當前fragment可見狀態 **/
    public boolean mCurrentFragmentVisibility = false;

    /**
     * fragment可見性變化時回調
     *
     * @param isVisibleToUser 當前fragment是否前臺可見
     * @param isLifeCycle     當前fragment可見性是否由fragment生命周期變化引起  false: 由調用{@link #setUserVisibleHint}引起
     */
    @CallSuper
    private void onFragmentVisibilityChanged(boolean isVisibleToUser, boolean isLifeCycle) {
        this.mCurrentFragmentVisibility = isVisibleToUser;
        Fragment fragment = getParentFragment();
        boolean fragmentVisible = false;
        if (fragment instanceof LazyLoadFragment) {
            //處理view非懶加載時有二級fragment viewpager情況第一次初始化時的問題
            //這種情況下一級fragment不可見,但二級viewpager中fragment初始化后會自動設置二級fragment的可見性
            fragmentVisible = ((LazyLoadFragment) fragment).mCurrentFragmentVisibility;
            if (!fragmentVisible && isLifeCycle) {
                return;
            }
        }

        this.onFragmentVisibilityChanged(isVisibleToUser);
    }
  1. 通過Fragment生命周期方法處理可見性回調

    在頁面跳轉以及Fragment進行了前后臺切換時,我們仍然希望能正確及時的知道Fragment的可見情況,比如頁面跳走或者切到后臺時Fragment應該是不可見的,在當頁面跳回來或者切到前臺時Fragment應該是可見的,而這些ViewPager不會給出相應的狀態,所以通過下面代碼就可以搞定:

    @CallSuper
     @Override
     public void onStart() {
         super.onStart();
         Log.e("LazyLoadFragment", "onStart -> mIsRealViewSetup: " + mIsRealViewSetup + " , originVisibleOfUserHint+ " + originVisibleOfUserHint + " ]]> " + getClass().getSimpleName());
    
         if (mIsRealViewSetup && originVisibleOfUserHint) {
             this.onFragmentVisibilityChanged(true, true);
         }
     }
    
     @CallSuper
     @Override
     public void onStop() {
         super.onStop();
    
         if (mIsRealViewSetup && originVisibleOfUserHint) {
             this.onFragmentVisibilityChanged(false, true);
         }
     }
  2. 有子集ViewPager嵌套時的處理

    我們經常會碰到這樣的情況,主頁面ViewPager有幾個Fragment,而其中有的Fragment又通過ViewPager嵌入了二級Fragment頁面,面對這樣的情況,上面的處理還有點欠缺,主要是因為這樣:ViewPager嵌入Fragment后,在初始化后,會自動或者被動切換到一個tab下,這內部也會調用 public void setUserVisibleHint(...) 設置可見性,這里存在的問題是他們并不知道父Fragment的可見狀態。

    那么我的需求正好是子Fragment可見時父Fragment的狀態也應該可見才對,那么面對這樣的需求,做了以下的處理:

    /**
      * fragment可見性變化時回調
      *
      * @param isVisibleToUser 當前fragment是否前臺可見
      * @param isLifeCycle     當前fragment可見性是否由fragment生命周期變化引起  false: 由調用{@link #setUserVisibleHint}引起
      */
     @CallSuper
     private void onFragmentVisibilityChanged(boolean isVisibleToUser, boolean isLifeCycle) {
         this.mCurrentFragmentVisibility = isVisibleToUser;
         Fragment fragment = getParentFragment();
         boolean fragmentVisible = false;
         if (fragment instanceof LazyLoadFragment) {
             //處理view非懶加載時有二級fragment viewpager情況第一次初始化時的問題
             //這種情況下一級fragment不可見,但二級viewpager中fragment初始化后會自動設置二級fragment的可見性
             fragmentVisible = ((LazyLoadFragment) fragment).mCurrentFragmentVisibility;
             if (!fragmentVisible && isLifeCycle) {
                 return;
             }
         }
    
         this.onFragmentVisibilityChanged(isVisibleToUser);
         Log.e("LazyLoadFragment", "onFragmentVisibilityChanged -> isVisibleToUser: " + isVisibleToUser + " , isLifeCycle: " + isLifeCycle + " , [-" + getClass().getSimpleName() + "-]" + " , parent: " + fragment.getClass().getSimpleName() + " = " + fragmentVisible);
    
         final ViewPager viewPager = this.setNestedViewPagerWithNestedFragment();
         if (null != viewPager) {
             this.handleNestedFragmentVisibilityWhenFragmentVisibilityChanged(viewPager, isVisibleToUser, isLifeCycle);
         }
     }
    
     /**
      * 處理在內外層viewpager里的fragment初始化后引起fragment可見性不一致問題(尤其開啟了view懶加載后,子viewpager并未處理外層fragment可見性)
      * 這里的處理是:
      * 1. 外層fragment不可見時,它內部的所有fragment都應該不可見
      * 2. 內部fragment可見時,他所關聯的父fragment也應該可見
      *
      * @param viewPager
      * @param isVisible
      * @param isLifeCycle
      */
     private void handleNestedFragmentVisibilityWhenFragmentVisibilityChanged(final ViewPager viewPager, boolean isVisible, boolean isLifeCycle) {
         Log.e("DEBUG", "onFragmentVisibilityChanged ---- ###  " + isVisible + " , " + isLifeCycle);
         if (null == viewPager || isLifeCycle) {
             return;
         }
         final FragmentPagerAdapter adapter = ((FragmentPagerAdapter) viewPager.getAdapter());
         if (isVisible == false) {
             //不可見的情況下,子viewpager里的所有fragment都不應該可見
             final int size = adapter.getCount();
             for (int i = 0; i < size; i++) {
                 Fragment fragment = adapter.getItem(i);
                 if (null == fragment) {
                     continue;
                 }
    
                 fragment.setUserVisibleHint(isVisible);
             }
         } else {
             Log.e("DEBUG", "onFragmentVisibilityChanged ---- " + viewPager.getCurrentItem());
             Fragment fragment = adapter.getItem(viewPager.getCurrentItem());
             if (null != fragment) {
                 fragment.setUserVisibleHint(isVisible);
             }
         }
     }
     /**
      * 適用與外層是viewpager(vp_outer) ,viewpager中某個fragment中也用了viewpager(vp_inner)時,用來設置vp_inner
      *
      * @return
      */
     protected ViewPager setNestedViewPagerWithNestedFragment() {
         return null;
     }

    子類通過復寫 protected ViewPager setNestedViewPagerWithNestedFragment() 方法配置關聯的ViewPager。

這樣,懶加載過程就算完成了,有問題就留言吧。

 

來自:http://www.jianshu.com/p/72bfc932d592

 

 本文由用戶 TesI55 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!