RecyclerView 源码分析1-绘制流程

1,584 阅读14分钟

在Android开发中RecyclerView是我们高频使用的一个组件,用来展示大量的数据。我们不仅要熟练使用它,还要对它的实现有一个认知。本片文章介绍RecyclerView的绘制流程,也就是onMeasureonLayoutonDraw这三个方法中主要做了些什么工作,let's go!

OnMeasure

我们知道该方法是测量当前View及子View的宽高,但是查看RecyclerView的源码发现代码很长,它不仅仅做了测量的工作,还做了一些其他的工作,我们一起来看一下

if (mLayout == null) {
   //第一种情况
}
if (mLayout.isAutoMeasureEnabled()) {
  //第二种情况,通常会进入到这种情况
}else{
 // 第三种情况
}

根据条件分支将onMeasure方法分成了三种情况,我们挨个来讨论一下

  • 情况一: mLayout == null
if (mLayout == null) {
    defaultOnMeasure(widthSpec, heightSpec);
    return;
}
void defaultOnMeasure(int widthSpec, int heightSpec) {
    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);
}
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);
    }
}

mLayout == null时,从传入的参数可以看出来RecyclerView没有考虑子View就决定了自己的大小,是一个比较粗糙的测量,具体的大小还需要根据之后多次测量的结果来定

  • 情况二 mLayout.isAutoMeasureEnabled() == true

mLayout.isAutoMeasureEnabled()true时,将调用LayoutManager.onLayoutChildren(Recycler, State)来计算子View们所需要的大小,RecyclerView.LayoutManager的实现类LinearLayoutManagerStaggeredGridLayoutManager都重载了该方法并返回true,所以通常都会走入这个分支(列出了部分代码)

if (mLayout.isAutoMeasureEnabled()) {
    //将测量交给LaytouManager
    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
    final boolean measureSpecModeIsExactly =
    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
    //如果width和height都已经是精确值,那么就不用再根据内容进行测量,后面步骤不再处理
    if (measureSpecModeIsExactly || mAdapter == null) {
        return;
    }
    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
    }
    dispatchLayoutStep2();
    //如果需要二次测量的话
    if (mLayout.shouldMeasureTwice()) {
    dispatchLayoutStep2();
    }
}

第一步调用了LayoutManager.onMeasure()方法

public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,int heightSpec) {
    mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}

该方法直接调用了RecyclerView的默认测量方法,就是我们之前分析的第一种情况。阅读该方法的注释我们可以发现,LayoutoManager强烈推荐开启自动测量,并且如果开启了自动测量就不要重写该方法,LayoutoManager的三个默认实现也确实没重写该方法。还介绍了下测量的策略,如果宽高测量模式为UNSPECIFIED. AT_MOST则指定为EXACTLY并且RecyclerView占用可用的最大空间。

第二步 如果宽高的测量模式都为MeasureSpec.EXACTLY或者没有设置Adapter直接返回。

接下来我们继续看,mState.mLayoutStep的默认值就是State.STEP_START,进入条件语句执行dispatchLayoutStep1(),然后执行dispatchLayoutStep2(),如果需要执行二次测量的话在执行一次dispatchLayoutStep2()

我们重点看dispatchLayoutStep1()dispatchLayoutStep2(),与这两个方法息息相关的一个变量是mState.mLayoutStep,该变量得意义是决定了dispatchLayoutStep1()dispatchLayoutStep1()dispatchLayoutStep2()这三个方法该执行哪一步了,它的取值有三个

mLayoutStep 描述
STEP_START 默认值或者dispatchLayoutStep3()已经执行了
STEP_LAYOUT dispatchLayoutStep1()已经执行了
STEP_ANIMATIONS dispatchLayoutStep2()已经执行了

对这三个方法的具体分析,我们放到onLayout中处理,先说一下结论dispatchLayoutStep1()处理了Adapter的数据更新以及准备动画前的数据;dispatchLayoutStep2()进行itemView的布局

  • 情况三: mLayout.isAutoMeasureEnabled() == false

    我们通常使用的LayoutManager都返回true,除非我们自定义,所以暂不分析这种情况

总结一下onMeasure所做的工作,假设LayoutManagerLinearLayoutManager

  1. 测量一下自己,可能需要多次测量
  2. 如果宽高不都为MeasureSpec.EXACTLY模式则执行
  • dispatchLayoutStep1(),处理Adapter更新以及准备动画前的数据
  • dispatchLayoutStep2()进行itemView的布局

OnLayout

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

就是执行了dispatchLayout

void dispatchLayout() {
        if (mAdapter == null) {
            Log.e(TAG, "No adapter attached; skipping layout");
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            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();
    }

流程也很清晰

  • 如果AdapterLayoutManager没设置就不进行布局,RecyclerView也就只能显示一片空白
  • 如果之前onMeasure中执行了dispatchLayoutStep1()dispatchLayoutStep2()则不再执行这两个方法,不过dispatchLayoutStep2()可能需要被再次调用
  • 执行dispatchLayoutStep3()

现在我们来查看下dispatchLayoutStep系列方法到底干了些什么

  • dispatchLayoutStep1()
/**
     * The first step of a layout where we;
     * - process adapter updates
     * - decide which animation should run
     * - save information about current views
     * - If necessary, run predictive layout and save its information
     */
    private void dispatchLayoutStep1() {
        processAdapterUpdatesAndSetAnimationFlags();
        if (mState.mRunSimpleAnimations){
            //...
        }
        if (mState.mRunPredictiveAnimations){
            //...
        }
        mState.mLayoutStep = State.STEP_LAYOUT;
    }

以上代码缩减了很多,从该方法的注释可以看出来,dispatchLayoutStep1()主要处理了Adapter更新以及准备动画所需数据,而processAdapterUpdatesAndSetAnimationFlags()就是用来处理Adapter更新和动画的Flag处理,我们看一下这个方法里面

private void processAdapterUpdatesAndSetAnimationFlags() {
    if (mDataSetHasChangedAfterLayout) {
            // Processing these items have no value since data set changed unexpectedly.
            // Instead, we just reset it.
            mAdapterHelper.reset();
            if (mDispatchItemsChangedEvent) {
                mLayout.onItemsChanged(this);
            }
        }
        为动画的flag进行赋值
        mState.mRunSimpleAnimations = ...
        mState.mRunPredictiveAnimations = ...
}

首先处理Adapter的更新(Adapter.notifyDataSetChanged()或者RecyclerView.swapAdapter(Adapter, boolean)代表Adapter有更新),就是简单的把之前记录的每一个item的操作重置一下,因为数据集的更改导致之前存的信息都没有意义了,下边的代码是为动画的标志位赋值,我们调用RecyclerView.setAdapterAdapter.notifyDataSetChanged()是不会触发动画的,所以我们先不考虑动画相关的东西。

我们继续来看dispatchLayoutStep1()的内容,下面是两个if条件,涉及两个变量mState.mRunSimpleAnimationsmState.mRunPredictiveAnimations这两个变量在要执行动画时才为true,所以先不考虑里面的内容。 最后执行mState.mLayoutStep = State.STEP_LAYOUT,代表dispatchLayoutStep1()已经执行完毕了

总结dispatchLayoutStep1() 处理了Adapter更新以及准备动画所需数据

  • dispatchLayoutStep2()
private void dispatchLayoutStep2() {
    mLayout.onLayoutChildren(mRecycler, mState);
    mState.mLayoutStep = State.STEP_ANIMATIONS;
}

方法很简洁,调用了LayoutManager.onLayoutChildren(Recycler recycler, State state),该方法就是进行子View布局的实质方法,不过是一个空实现,子类必须去实现这个方法,声明如下

public void onLayoutChildren(Recycler recycler, State state) {
    Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, Statestate) ");
}

最后mState.mLayoutStep = State.STEP_ANIMATIONS代表dispatchLayoutStep2()已经执行完毕了

总结dispatchLayoutStep2() 调用LayoutManager.onLayoutChildren来进行子View的布局

  • dispatchLayoutStep3()
private void dispatchLayoutStep3() {
    mState.mLayoutStep = State.STEP_START;
    if (mState.mRunSimpleAnimations) {
    //记录layout之后View的信息,并触发动画
    //...
    }
    //...一些清理工作
}

首先将mState.mLayoutStep = State.STEP_START,标志dispatchLayoutStep3()已经执行了,然后mState.mRunSimpleAnimations这个变量表示是否执行动画,第一次布局的时候是不需要动画的所以不会进入这个分支,动画我们之后在讲,最后做一些清理的工作。

总结dispatchLayoutStep3() 触发动画

总结一下onLayout所做的工作,大体流程如下

  1. dispatchLayoutStep1()处理了Adapter更新以及准备动画所需数据
  2. dispatchLayoutStep2()调用LayoutManager.onLayoutChildren来进行子View的布局
  3. dispatchLayoutStep3()触发动画

draw

    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);
        }
     //...处理clipToPadding="false"的情况
    }

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

总结:drawonDrawRecyclerView的分割线进行了绘制,当然分割线需要我们自己去实现具体的绘制内容,同时我们也知道了ItemDecorationonDrawonDrawOver的区别

至此我们已经完成了RecyclerView三大流程的源码分析,上面列出的代码大多都经过了精简,省去了很多细节,不过刚开始阅读源码时,我们只要把握整体的流程就好,抛开细节来看,以上的整体流程并不难理解。但是有一个很重要的方法没有细讲,就是LayoutManager.onLayoutChildren(),该方法才是布局子View的核心,我们对该方法进行单独的一波分析,以LinearLayoutManager(只考虑竖直方向)为例来看一下

LinearLayoutManager.onLayoutChildren()

在进行子View的布局中利用了一些帮助类来帮助布局,我们需要先了解一下这些帮助类

  • LayoutState
属性 解释
mOffset 偏移多少个像素点之后开始布局
mAvailable 当前布局方向上可用的空间
mCurrentPosition 要布局子View在Adapter中的代表的位置
mInfinite 布局的View数量没有限制
  • AnchorInfo
属性 解释
mPosition 锚点位置
mCoordinate 锚点坐标信息
mValid 是否可用
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // layout algorithm:
        // 1) by checking children and other variables, find an anchor coordinate and an anchor
        //  item position.
        // 2) fill towards start, stacking from bottom
        // 3) fill towards end, stacking from top
        // 4) scroll to fulfill requirements like stack from bottom.

    //新建一个LayoutState
    ensureLayoutState();
    if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION || mPendingSavedState != null) {
            mAnchorInfo.reset();
            //更新锚点信息
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            mAnchorInfo.mValid = true;
    }
    //...计算LinearLayoutManager所需的额外空间
    //锚点信息准备好了
    onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
    //将现有的子View都缓存起来
    detachAndScrapAttachedViews(recycler);
    if (mAnchorInfo.mLayoutFromEnd){
        //...
    }else{
        // fill towards end
        //将锚点的信息保存到mLayoutState中
        updateLayoutStateToFillEnd(mAnchorInfo);
        fill(recycler, mLayoutState, state, false);
        // fill towards start
        updateLayoutStateToFillEnd(mAnchorInfo);
        fill(recycler, mLayoutState, state, false);
        
        if (mLayoutState.mAvailable > 0) {
            fill(recycler, mLayoutState, state, false);
        }
    }
}

以上代码省略了很多有用信息,包括对LayoutState内部一些有用属性的赋值等。由代码刚开始的注释可了解到该方法内部执行逻辑

  1. 找到锚点位置和锚点坐标
  2. 从锚点开始,往上填充布局子View,直到填满区域
  3. 从锚点开始,往下填充布局子View,直到填满区域
  4. 滚动以满足需求,如从底部堆叠

关键是锚点,对于LinearLayoutManager来说,它不一定是从最高点或者最低点开始布局,有可能是从中间某个点开始布局的,如图所示

原图出自RecyclerView剖析,

  • 确定锚点信息
    1. 第一次布局,锚点信息肯定是不可用的,进入更新锚点的条件语句中,里面调用updateAnchorInfoForLayout(recycler, state, mAnchorInfo)更新锚点信息
    private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
            AnchorInfo anchorInfo) {
        // 第一种计算方式
        if (updateAnchorFromPendingData(state, anchorInfo)) {
            if (DEBUG) {
                Log.d(TAG, "updated anchor info from pending information");
            }
            return;
        }
        // 第二种计算方式
        if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
            if (DEBUG) {
                Log.d(TAG, "updated anchor info from existing children");
            }
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "deciding anchor info for fresh state");
        }
        // 第三种计算方式
        anchorInfo.assignCoordinateFromPadding();
        anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
    }

可以看出来有三种计算锚点信息的方法,每个方法里的代码虽然多却不难理解

  • 方法一
private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
        if (state.isPreLayout() || mPendingScrollPosition == RecyclerView.NO_POSITION) {
            return false;
        }
        //...
}

mPendingScrollPosition == RecyclerView.NO_POSITION这个条件判断默认情况是true的,只有在调用scrollToPosition或者在onRestoreInstanceState恢复之前记录的mPendingScrollPosition时才会有其他值(上面省略代码就是计算mPendingScrollPosition不为默认值的锚点信息,本文没有分析),所以默认情况该方法没有计算锚点信息,往下走

  • 方法二 updateAnchorFromChildren这个方法根据子View来确定锚点信息
    • 如果没有子View则,直接返回false,表示没有计算出锚点信息
    • 有子View的话,一般会选择屏幕中可见的子View的position为锚点。这里会选取屏幕上第一个可见View,也就是positon=1的子View作为参考点, anchorInfo.mCoordinate 被赋值为1号子View上面的Decor的顶部位置

该方法的详细分析可以看这篇文章RecyclerView源码解析

  • 方法三

最后兜底的方法,直接将anchorInfo.mCoordinate赋值为padding,如果没有设置padding,则anchorInfo.mCoordinate = 0anchorInfo.mPosition = 0(mStackFromEnd == false的情况,该值默认是false)

锚点信息的计算主要是为mPositionmCoordinate这两个变量赋值,这样我们就知道了从哪个点开始填充子View和子View对应的数据在Adapter中的位置

更新锚点信息之后,源码中有一长段代码用来计算LinearLayoutManager需要的“额外空间”,这段代码我也没懂,就不分析了,不过并不影响我们把握整体布局流程。锚点信息都准备好之后,updateLayoutStateToFillEnd()将锚点信息保存到mLayoutState中,然后调用fill()方法开始填充子View了,mAnchorInfo.mLayoutFromEnd将填充分为两种情况

  • true: 从Adapter最后一项开始,从下往上布局
  • false: 从Adapter第一项开始,从上往下布局(默认情况) 如图所示(虚线表示屏幕外边的ItemView

默认情况为false,从上往下开始布局,然后进入关键的fill()方法

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
    //可用空间
    final int start = layoutState.mAvailable;
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    //保存了每一子View消耗的空间
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    //循环布局子View
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            //核心方法
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if (layoutChunkResult.mFinished) {
                break;
            }
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                    || !state.isPreLayout()) {  
                layoutState.mAvailable -= layoutChunkResult.mConsumed;
                // we keep a separate remaining space because mAvailable is important for recycling
                //消费子View占据的空间
                remainingSpace -= layoutChunkResult.mConsumed;
            }
         //...
    }      
return start - layoutState.mAvailable;
}

fill的核心思路就是在一个循环里不断地进行子View布局,结束条件是没有可用空间或者数据源没有数据了,layoutChunk负责填充,每填充一个子View,剩余空间就减相对应View占据的空间(竖直方向上来说就是高度),然后填充下一个,最后返回的是本次布局所填充的区域大小。

我们进入layoutChunk来看具体的实现

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        //获取一个子View
        View view = layoutState.next(recycler);
        if (view == null) {
            if (DEBUG && layoutState.mScrapList == null) {
                throw new RuntimeException("received null view when unexpected");
            }
            // if we are laying out views in scrap, this may return null which means there is
            // no more items to layout.
            result.mFinished = true;
            return;
        }
        //将View添加到RecyclerView中
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        //测量子View的大小包括margins和decorations
        measureChildWithMargins(view, 0, 0);
        //将占据的空间保存到LayoutChunkResult之中,供外层循环使用
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.
        //将子View放到合适的位置   
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        if (DEBUG) {
            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                    + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                    + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
        }
        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.hasFocusable();
    }

  • 第一步 View view = layoutState.next(recycler)获取一个itemView,这里涉及到RecyclerView的缓存机制,我们后边的篇章在讨论。
  • 第二步 是将itemView添加到RecyclerView中,又分为两种情况
    • addView 很好理解,方法内部做了一些位置正确性、避免重复添加等逻辑判断,然后调用ViewGroupaddView来实现。
    • addDisappearingView 代表该View即将从屏幕上消失,比如划出屏幕或者调用Adapter.notifyItemRemoved,该方法和上面的addView都会调用内部的addViewInt(View child, int index, boolean disappearing),只不过是最后一个参数不一样而已。
  • 第三步 测量itemView的大小,measureChildWithMargins(view, 0, 0)这个方法内部除了自身大小之外,还需要考虑margindecorations(我们常说的分割线)的大小。测量之后把消耗的空间保存到LayoutChunkResult之中,供外层循环使用。
  • 第四步 将itemView放到合适的位置,计算位置时layoutState.mOffset跟我们之前算的锚点坐标息息相关,如果是第一个itemView,则layoutState.mOffset和锚点坐标是一样的,大家可以通过调试来观察数据对应关系。当然布局时还了考虑margindecorations(我们常说的分割线)

以上将fill()方法分析完成之后,LinearLayoutManager.onLayoutChildren的核心 已经分析完毕了,最后还有一个layoutForPredictiveAnimations,从该方法的注释来看,是为了动画做一些布局,也不是必须执行的,就不再分析了,如果有读者清楚这块内容的,希望能告知我一下。

至此,LinearLayoutManager.onLayoutChildren分析完毕,但是该方法注释的最后一条,贴一下原文 4) scroll to fulfill requirements like stack from bottom.我并没有看到它体现在哪,可能是上文省略的一些细节中包含,总之这一点我并不明白,如果有读者清楚,希望能告知我一下。

总结

RecyclerView的绘制流程我们分析完了,总结一下

  1. onMeasureLayoutManager是否开启自动测量是有关系的,如果支持自动测量的话,可能会进行预布局,默认实现的三个LayoutManager都是支持自动测量的,如果自定义LayoutManager的话要注意这一点
  2. onLayout 中主要是dispatchLayoutStep1()dispatchLayoutStep1()dispatchLayoutStep1()这三个方法按顺序调用,第一个和第三个主要处理了动画相关,第二个将布局的任务交给LayoutManager
  3. drawonDraw调用了ItemDecoration中的方法,我们实现这些方法来自定义分割线

最后,由于作者水平有限,如果以上分析有出错的地方,欢迎提出,我及时进行改正,以免误导其他人

相关资料

RecyclerView 源码分析(一) - RecyclerView的三大流程

RecyclerView源码解析

RecyclerView剖析