RecyclerView 源码分析

RecyclerView本身是一个展示大量数据的控件,相比较ListView,RecyclerView的4级缓存表现的非常出色,在性能方面相比于ListView提升了不少。同时由于LayoutManager的存在,让RecyclerView不仅有ListView的特点,同时兼有GridView的特点。
RecyclerView在设计方面上也是非常的灵活,不同的部分承担着不同的职责。其中Adapter负责提供数据,包括创建ViewHolder和绑定数据,LayoutManager负责ItemView的测量和布局,ItemAnimator负责每个ItemView的动画,ItemDecoration负责每个ItemView的间隙。这种插拔式的架构使得RecyclerView变得非常的灵活,每一个人都可以根据自身的需求来定义不同的部分。

1.measure

首先分析measure过程,分析RecyclerViewonMeasure方法
mLayoutLayoutManager的对象。当RecyclerViewLayoutManager为空时,RecyclerView不能显示任何的数据;第二种情况是LayoutManager开启了自动测量,有可能会测量两次。第三种情况就是没有开启自动测量,这种情况比较少,因为为了RecyclerView支持warp_content属性,系统提供的LayoutManager都开启自动测量的

    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            // 第一种情况
        }
        if (mLayout.isAutoMeasureEnabled()) {
            // 第二种情况
        } else {
            // 第三种情况
        }
    }

1.1 LayoutManager为空

源码
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }

直接调了defaultOnMeasure方法
    void defaultOnMeasure(int widthSpec, int heightSpec) {
        // calling LayoutManager here is not pretty but that API is already public and it is better
        // than creating another method since this is internal.
        final int width = LayoutManager.chooseSize(widthSpec,
                getPaddingLeft() + getPaddingRight(),
                ViewCompat.getMinimumWidth(this));
        final int height = LayoutManager.chooseSize(heightSpec,
                getPaddingTop() + getPaddingBottom(),
                ViewCompat.getMinimumHeight(this));

        setMeasuredDimension(width, height);
    }

defaultOnMeasure方法里面,先是通过LayoutManagerchooseSize方法来计算值,然后就是setMeasuredDimension方法来设置宽高

        public static int chooseSize(int spec, int desired, int min) {
            final int mode = View.MeasureSpec.getMode(spec);
            final int size = View.MeasureSpec.getSize(spec);
            switch (mode) {
                case View.MeasureSpec.EXACTLY:
                    return size;
                case View.MeasureSpec.AT_MOST:
                    return Math.min(size, Math.max(desired, min));
                case View.MeasureSpec.UNSPECIFIED:
                default:
                    return Math.max(desired, min);
            }
        }

LayoutManager为空时,不会显示任何数据,因为当RecyclerView处于onLayout阶段,会调用dispatchLayout方法。而在dispatchLayout方法里面有这么一行代码:

        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            // leave the state in START
            return;
        }

1.2 当LayoutManager开启了自动测量

RecyclerView的测量分为两步,分别调用dispatchLayoutStep1dispatchLayoutStep2。而dispatchLayoutStep3方法的调用在RecyclerViewonLayout方法里面。在这种情况下,只会调用dispatchLayoutStep1dispatchLayoutStep2这两个方法。

然后接触一个变量mState.mLayoutStep

取值含义
State.STEP_STARTmState.mLayoutStep的默认值,表示RecyclerView还未经历dispatchLayoutStep1dispatchLayoutStep1调用之后该值会变为State.STEP_LAYOUT。
State.STEP_LAYOUT表示此时处于layout阶段,这个阶段会调用dispatchLayoutStep2方法。调用dispatchLayoutStep2方法之后,该值变为了State.STEP_ANIMATIONS
State.STEP_ANIMATIONS该值表示RecyclerView处于第三个阶段,也就是执行动画的阶段,也就是调用dispatchLayoutStep3方法。当dispatchLayoutStep3方法执行完毕之后,该值又变为了State.STEP_START

接下来是源码分析:

        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;
            }

            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }
            // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
            // consistency
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();

            // now we can get the width and height from the children.
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

            // if RecyclerView has non-exact width and height and if there is at least one child
            // which also has non-exact width & height, we have to re-measure.
            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);
            }
        }

1.调用LayoutManageronMeasure方法进行测量。默认是调用RecyclerViewdefaultOnMeasure
2.如果mState.mLayoutStepState.STEP_START的话,那么就会执行dispatchLayoutStep1方法,然后会执行dispatchLayoutStep2方法。
3.如果需要第二次测量的话,会再一次调用dispatchLayoutStep2 方法。

方法名作用
dispatchLayoutStep1三大dispatchLayoutStep方法第一步。本方法的作用主要有三点:1.处理Adapter更新;2.决定是否执行ItemAnimator;3.保存ItemView的动画信息。本方法也被称为preLayout(预布局),当Adapter更新了,这个方法会保存每个ItemView的旧信息(oldViewHolderInfo)
dispatchLayoutStep2第二步。在这个方法里面真正进行children的测量和布局
dispatchLayoutStep3第三步。这个方法的作用执行在dispatchLayoutStep1方法里面保存的动画信息

dispatchLayoutStep1源码可以得出结论,RecyclerView第一次加载数据时,是不会执行的动画。dispatchLayoutStep2方法中,一方面AdaptergetItemCount方法被调用,二是调用了LayoutManageronLayoutChildren方法,这个方法里面进行对children的测量和布局。系统的LayoutManageronLayoutChildren方法是一个空方法,所以需要LayoutManager的子类自己来实现。从而来决定RecyclerView在该LayoutManager的策略下,应该怎么布局。

1.3 没有开启自动测量

            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            // custom onMeasure
            if (mAdapterUpdateDuringMeasure) {
                startInterceptRequestLayout();
                onEnterLayoutOrScroll();
                processAdapterUpdatesAndSetAnimationFlags();
                onExitLayoutOrScroll();

                if (mState.mRunPredictiveAnimations) {
                    mState.mInPreLayout = true;
                } else {
                    // consume remaining updates to provide a consistent state with the layout pass.
                    mAdapterHelper.consumeUpdatesInOnePass();
                    mState.mInPreLayout = false;
                }
                mAdapterUpdateDuringMeasure = false;
                stopInterceptRequestLayout(false);
            } else if (mState.mRunPredictiveAnimations) {
              
                setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
                return;
            }

            if (mAdapter != null) {
                mState.mItemCount = mAdapter.getItemCount();
            } else {
                mState.mItemCount = 0;
            }
            startInterceptRequestLayout();
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            stopInterceptRequestLayout(false);
            mState.mInPreLayout = false; // clear

如果mHasFixedSize为true(也就是调用了setHasFixedSize方法),将直接调用LayoutManageronMeasure方法进行测量。
如果mHasFixedSize为false,同时此时如果有数据更新,先处理数据更新的事务,然后调用LayoutManageronMeasure方法进行测量

2.layout

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
    }

onLayout方法重点操作还是在dispatchLayout方法里面。

    void dispatchLayout() {
        if (mAdapter == null) {
            Log.e(TAG, "No adapter attached; skipping layout");
            // leave the state in START
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            // leave the state in START
            return;
        }
        mState.mIsMeasuring = false;
        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();
    }

dispatchLayout方法保证RecyclerView必须经历三个过程–dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3。也就是说,在RecyclerView经历一次完整的dispatchLayout之后,后续如果参数有所变化时,可能只会经历最后的1步或者2步。

private void dispatchLayoutStep3() {
    // ······
    mState.mLayoutStep = State.STEP_START;
    // ······
}

这个方法主要是做Item的动画,也就是ItemAnimator的执行。在这里,我们需要关注dispatchLayoutStep3方法的是,它将mLayoutStep重置为了State.STEP_START。也就是说如果下一次重新开始dispatchLayout的话,那么肯定会经历dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3三个方法。
RecyclerView跟其他ViewGroup不同的地方在于,如果开启了自动测量,在measure阶段,已经将Children布局完成了;如果没有开启自动测量,则在layout阶段才布局Children。

3.draw

RecyclerViewdraw分为三步:

  1. 调用super.draw方法。这里主要做了两件事:
    1. 将Children的绘制分发给ViewGroup;
    2. 将分割线的绘制分发给ItemDecoration。
  2. 如果需要的话,调用ItemDecoration的onDrawOver方法。通过这个方法,我们在每个ItemView上面画上很多东西。
  3. 如果RecyclerView调用了setClipToPadding,会实现一种特殊的滑动效果–每个ItemView可以滑动到padding区域。

源码分析:

    public void draw(Canvas c) {
        // 第一步
        super.draw(c);
        // 第二步
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
        // 第三步
        // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we
        // need find children closest to edges. Not sure if it is worth the effort.
        // ······
    }

第一步会回调到onDraw方法中,关于children的绘制和ItemDecoration的绘制,是在onDraw方法里面实现的。

    @Override
    public void onDraw(Canvas c) {
        super.onDraw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }

调用super.onDraw方法将Children的绘制分发给ViewGroup执行;然后将ItemDecoration的绘制分发到ItemDecorationonDraw方法里面去。

4.总结

  1. RecyclerView的measure过程分为三种情况,每种情况都有执行过程。通常来说,我们都会走自动测量的过程。
  2. 自动测量里面需要分清楚mState.mLayoutStep状态值,因为根据不同的状态值调用不同的dispatchLayoutStep方法。
  3. layout过程也根据mState.mLayoutStep状态来调用不同的dispatchLayoutStep方法
  4. draw过程主要做了四件事:
    1. 绘制ItemDecoration的onDraw部分;
    2. 绘制Children;
    3. 绘制ItemDecoration的drawOver部分;
    4. 根据mClipToPadding的值来判断是否进行特殊绘制。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值