RecyclerView 源码分析1-绘制流程

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

OnMeasure

咱们知道该方法是测量当前View及子View的宽高,可是查看RecyclerView的源码发现代码很长,它不只仅作了测量的工做,还作了一些其余的工做,咱们一块儿来看一下缓存

if (mLayout == null) {
   //第一种状况
}
if (mLayout.isAutoMeasureEnabled()) {
  //第二种状况,一般会进入到这种状况
}else{
 // 第三种状况
}
复制代码

根据条件分支将onMeasure方法分红了三种状况,咱们挨个来讨论一下bash

  • 状况一: 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就决定了本身的大小,是一个比较粗糙的测量,具体的大小还须要根据以后屡次测量的结果来定app

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

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

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直接返回。post

接下来咱们继续看,mState.mLayoutStep的默认值就是State.STEP_START,进入条件语句执行dispatchLayoutStep1(),而后执行dispatchLayoutStep2(),若是须要执行二次测量的话在执行一次dispatchLayoutStep2()动画

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

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剖析

相关文章
相关标签/搜索