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。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容