RecyclerView的布局原理及四级缓存

 在Android应用开发过程中,只要是涉及到列表类的数据加载,RecyclerView的使用都是必不可少的。那么RecyclerView有哪些值得我们借鉴的地方呢?让我们来一探究竟。

一、测量和布局:

RecyclerView测绘布局有三步,分别为start,layout和animation。对应的方法为dispatchLayoutStep1()、dispatchLayoutStep2()、dispatchLayoutStep3()。 

具体看源码,首先,我们先进行开始测绘布局前的准备工作,从recyclerView的setLayoutManager开始,从这里可以看出主要做了两件事情,一是判断当前的RecyclerView有无依赖的LayoutManager,如果有就清除掉。二是判断当前的LayoutManager是否依赖在了其他的RecyclerView上,如果有就直接抛出异常

       if (mLayout != null) {
            mRecycler.clear();
            if (mIsAttached) { //移除依赖
                mLayout.dispatchDetachedFromWindow(this, mRecycler);
            }
            mLayout.setRecyclerView(null);
            mLayout = null;
        }else{
            mRecycler.clear();
        }

        if (layout != null) {
            if (layout.mRecyclerView != null) {
                throw new IllegalArgumentException("LayoutManager " + layout
                        + " is already attached to a RecyclerView:"
                        + layout.mRecyclerView.exceptionLabel());
            }
            mLayout.setRecyclerView(this);
            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this);
            }
        }

紧接着,由于RecyclerView继承的是ViewGroup,所以他的测绘布局动作也是我们重要看的。首先看onMeasure()测量方法。这里主要做了2件事情,

1:在某些极端情况下计算出尽可能合理的宽高值,进行兜底的操作

2:开启测量,布局的阶段,主要是dispatchLayoutStep2方法。此方法里面主要进行了item的布局工作,其中,至关重要的一个方法是执行了RecyclerView的onLayoutChildren方法,LinearLayoutManager,GridLayoutManager等布局管理器会实现此方法,进行具体的布局逻辑

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        //1:layoutManager 若为空,则无法进行测量,这时候会通过默认方法,尽可能的给出准确的尺寸
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }

        //开启自动测量
        if (mLayout.isAutoMeasureEnabled()) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);

            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

            final boolean measureSpecModeIsExactly =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            if (measureSpecModeIsExactly || mAdapter == null) {
                return;
            }

            //这里主要对需要做动画的item进行收集和整理,完成后会把mState.mLayoutStep赋值成 
            //STEP_LAYOUT
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }

            //开始测量,并布局列表上的item,从而期望能够计算出RecyclerView的宽高
            dispatchLayoutStep2();

            //如果RecyclerView没有准确的宽高,并且至少有一个item也没有固定的宽高,则对再走一 
            //遍。 dispatchLayoutStep2(),进行更加准确的测量,从而能够得出RecyclerView的宽 
            //高。从这里我们也可以看出,在实际开发过程中尽肯能的对RecyclerView的宽高设置为精确 
            //的值。这里这行完成后,状态会被赋值成STEP_ANIMATIONS,开始第3阶段
            if (mLayout.shouldMeasureTwice()) {
                mLayout.setMeasureSpecs(
                        MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } 
    }

 最后,测量完成结束,会走到onLayout()回调中,如果前面没有开启自动测量,这里会再重新走一次dispatchLayoutStep1和2,再执行dispatchLayoutStep3,dispatchLayoutStep3里主要进行了动画执行的逻辑。

// onLayout的dispatchLayout()方法
 void dispatchLayout() {
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            // First 2 steps are done in onMeasure but looks like we have to run 
            //again due to
            // changed size.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }

二、四级缓存

1:第一级缓存,不需要重新bindViewHolder。ArrayList<ViewHolder> mAttachedScrap; 保存的是屏幕上没有任何变化的ViewHolder, ArrayList<ViewHolder> mChangedScrap保存的是有变化ViewHolder,比如刷新了某个item那个此ViewHolder会被保存到mChangedScrap中

2:第二级缓存,不需要重新bindViewHolder,需要进行位置一致性校验,随着列表的上下滑动,被划出屏幕的ViewHolder就会储存到这里;可通过setItemCacheSize调整,默认大小为2;ArrayList<ViewHolder> mCacheViews;

3:自定义拓展View缓存,ViewCacheExtension mViewCacheExtension;可忽略

4:第四级缓存,当且仅当mCacheViews放不下的时候,会放入这个缓存里。根据viewType存取ViewHolder,可通过setRecycledViewPool调整,每个类型容量默认为5;RecycledViewPool mRecyclerPool;

三、复用机制

LayoutManager每次向列表上填充item的时候,会向Recycler索取ViewHolder优先级非别为mAttachScrp和mChangedScrp(屏幕内的ViewHolder,不需要重新绑定数据),mCacheViews(滑出屏幕的ViewHolder,也是不需要重新进行数据绑定,需要进行位置一致性校验,判断滑出屏幕的ViewHolder是否和需要现在重新放置的ViewHolder位置一致)和mRecyclerPool(需要重新进行数据绑定)。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值