Fragment View 懒加载

这个只是一个简单的类,具体放在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;
    }
  1. 懒加载初始化及通过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);
        }
    }
  1. 有子集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。

这样,懒加载过程就算完成了,有问题就留言吧。

你可能感兴趣的:(Fragment View 懒加载)