RecyclerView绘制过程分析

一 相关类的作用

RecyclerView具有很好的可扩展性。我们可以自定义子view的排列,可以指定动画和分割线,另外RecyclerView还有一套自己的缓存机制。RecyclerView将不同的工作交给不同的类去处理。

LayoutoutManager

LayoutoutManager是RecyclerView中的一个抽象静态内部类。LayoutManager负责RecyclerView的布局,其中包含了Item View的获取与回收。

LayoutManager中有几个抽象方法非常重要:

  • onLayoutChildren(): 对RecyclerView进行布局的入口方法。

  • fill(): 负责填充RecyclerView。

  • scrollVerticallyBy():根据手指的移动滑动一定距离,并调用fill()填充。

  • canScrollVertically()或canScrollHorizontally(): 判断是否支持纵向滑动或横向滑动。

ItemDecoration

ItemDecoration 是为了显示每个 item 之间分隔样式的。它的本质实际上就是一个 Drawable。当 RecyclerView 执行到 onDraw() 方法的时候,就会调用到他的 onDraw(),这时,如果你重写了这个方法,就相当于是直接在 RecyclerView 上画了一个 Drawable 表现的东西。 而最后,在他的内部还有一个叫getItemOffsets()的方法,从字面就可以理解,他是用来偏移每个 item 视图的。当我们在每个 item 视图之间强行插入绘画了一段 Drawable,那么如果再照着原本的逻辑去绘 item 视图,就会覆盖掉 Decoration 了,所以需要getItemOffsets()这个方法,让每个 item 往后面偏移一点,不要覆盖到之前画上的分隔样式了。

ItemAnimator

每一个 item 在特定情况下都会执行的动画。说是特定情况,其实就是在视图发生改变,我们手动调用notifyxxxx()的时候。通常这个时候我们会要传一个下标,那么从这个标记开始一直到结束,所有 item 视图都会被执行一次这个动画。

Adapter

首先是适配器,适配器的作用都是类似的,用于提供每个 item 视图,并返回给 RecyclerView 作为其子布局添加到内部。

但是,与 ListView 不同的是,ListView 的适配器是直接返回一个 View,将这个 View 加入到 ListView 内部。而 RecyclerView 是返回一个 ViewHolder 并且不是直接将这个 holder 加入到视图内部,而是加入到一个缓存区域,在视图需要的时候去缓存区域找到 holder 再间接的找到 holder 包裹的 View。

ViewHolder

每个 ViewHolder 的内部是一个 View,并且 ViewHolder 必须继承自RecyclerView.ViewHolder类。 这主要是因为 RecyclerView 内部的缓存结构并不是像 ListView 那样去缓存一个 View,而是直接缓存一个 ViewHolder ,在 ViewHolder 的内部又持有了一个 View。既然是缓存一个 ViewHolder,那么当然就必须所有的 ViewHolder 都继承同一个类才能做到了。

二 绘制流程

RecyclerView的声明如下:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {} 

可见,RecyclerView本质还是一个ViewGroup,那么其绘制流程肯定遵循view的绘制流程:onMeasure - onLayout - onDraw。那么就去看看这几个方法里做了些什么。

onMeasure

首先要了解下mLayout是什么,它是一个LayoutManager,RecyclerView中的一个成员变量,通过setLayoutManager方法赋值:

public void setLayoutManager(LayoutManager layout) {
    if (layout == mLayout) {
        return;
    }
    .......
    mLayout = layout;
    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);
        }
    }
    mRecycler.updateViewCacheSize();
    requestLayout();
}

可以看到,我们在使用RecyclerView中,setLayoutManager就是把我们自定义的LayoutManager传递给了mLayout。

回到正题,下面是RecyclerView中onMeasure的源码:

protected void onMeasure(int widthSpec, int heightSpec) {
    /** 1.判断mLayout是否为null,为null执行defaultOnMeasure,return **/
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    /** 2.isAutoMeasureEnabled()一般都是true **/
    if (mLayout.isAutoMeasureEnabled()) {
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        /** 3.调用了LayoutManager的onMeasure **/
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        /** 4.当RecyclerView的宽高都是EXACTLY时,skipMeasure为true,那么接下来直接return **/
        final boolean measureSpecModeIsExactly =
                widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
        if (measureSpecModeIsExactly || mAdapter == null) {
            return;
        }
        /** 5.mState.mLayoutStep为STEP_START,这里会执行dispatchLayoutStep1(); **/
        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;
        /** 6.执行dispatchLayoutStep2();这个方法很重要,下面继续分析 **/
        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);
        }
    } else {
        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) {
            // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
            // this means there is already an onMeasure() call performed to handle the pending
            // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
            // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
            // because getViewForPosition() will crash when LM uses a child to measure.
            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
    }
}

代码里注释了重要的步骤,应该都挺好理解的。下面详细分析下。

1.mLayout为null时,defaultOnMeasure(widthSpec, heightSpec);做了什么?

/** 
* An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios 
* where this RecyclerView is otherwise lacking better information. 
*/ 
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);
}

mLayout为null说明我们没有指定RecyclerView的子view如何布局和绘制,那么这里就是很常规的根据SpecMode设置RecyclerView的宽高,其它什么都没干,这也是我们不调用setLayoutManager方法导致RecyclerView空白的效果。

2.isAutoMeasureEnabled()

/**Returns whether the measuring pass of layout should use the AutoMeasure mechanism of
{@link RecyclerView} or if it should be done by the LayoutManager's implementation of
{@link LayoutManager#onMeasure(Recycler, State, int, int)}.
*/
public boolean isAutoMeasureEnabled() {
    return mAutoMeasure;
}

也就是说,返回true,那么执行RecyclerView的autoMeasure机制,否则就执行LayoutManager的实现的onMeasure方法。LinearLayoutManager的这个参数为true。

3.mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);的作用

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

验证了第二步,这里执行mRecyclerView的defaultOnMeasure(widthSpec, heightSpec);这个方法最后会执行setMeasuredDimension(width, height);

4.measureSpecModeIsExactly 为true时,为什么直接return了?

final boolean measureSpecModeIsExactly =
                widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;

当widthMode和heightMode为EXACTLY时,measureSpecModeIsExactly 为true,这个时候,onMeasure可以直接返回了。为什么呢?因为当widthMode和heightMode都为EXACTLY时,说明RecyclerView的宽高都是固定的,不用根据子view的宽高来确定自身的宽高,所以执行完第3步,其实RecyclerView就已经确定大小了。

5.dispatchLayoutStep1()和dispatchLayoutStep2()

其实还有个dispatchLayoutStep3(),这三个方法才是RecyclerView的绘制流程。下面是三个方法的具体作用:

  • dispatchLayoutStep1: Adapter的更新; 决定该启动哪种动画; 保存当前View的信息(getLeft(), getRight(), getTop(), getBottom()等); 如果有必要,先跑一次布局并将信息保存下来。

  • dispatchLayoutStep2: 真正对子View做布局的地方。

  • dispatchLayoutStep3: 为动画保存View的相关信息; 触发动画; 相应的清理工作。

这里还牵扯到了mState.mLayoutStep,这个变量在每个step值都是不一样的,具体表现如下:

  • 初始化为STEP_START

  • 执行完dispatchLayoutStep1后,mState.mLayoutStep = State.STEP_LAYOUT;

  • 执行完dispatchLayoutStep2后,mState.mLayoutStep = State.STEP_ANIMATIONS;

  • 执行完dispatchLayoutStep3后,mState.mLayoutStep = State.STEP_LAYOUT;

所以我们可以根据mState.mLayoutStep的值来确定我们处于哪个状态。

onLayout

接下来分析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");
        // 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();
}

这里会根据mState.mLayoutStep判断是否要执行dispatchLayoutStep1();和dispatchLayoutStep2();方法,最后调用dispatchLayoutStep3();。

在onMeasure和onLayout过程中,RecyclerView将大部分的逻辑都交给了dispatchLayoutStep1()、dispatchLayoutStep2()和dispatchLayoutStep3()处理,那么就针对这三个方法分析一下。

dispatchLayoutStep1()
/**
* The first step of a layout where we;
* - process adapter updates  (处理Adapter更新)
* - decide which animation should run   (决定执行哪个动画)
* - save information about current views    (保存当前view的一些信息)
* - If necessary, run predictive layout and save its information
*/
private void dispatchLayoutStep1() {
    .......
    /**处理Adapter和动画相关**/
    processAdapterUpdatesAndSetAnimationFlags();
    .......
    if (mState.mRunSimpleAnimations) {
        // Step 0: Find out where all non-removed items are, pre-layout
        int count = mChildHelper.getChildCount();
        for (int i = 0; i < count; ++i) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
            if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                continue;
            }
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPreLayoutInformation(mState, holder,
                            ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                            holder.getUnmodifiedPayloads());
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                    && !holder.shouldIgnore() && !holder.isInvalid()) {
                long key = getChangedHolderKey(holder);
                mViewInfoStore.addToOldChangeHolders(key, holder);
            }
        }
    }
    if (mState.mRunPredictiveAnimations) {
        saveOldPositions();
        final boolean didStructureChange = mState.mStructureChanged;
        mState.mStructureChanged = false;
        /**对子view进行布局**/
        // temporarily disable flag because we are asking for previous layout
        mLayout.onLayoutChildren(mRecycler, mState);
        mState.mStructureChanged = didStructureChange;
        for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
            final View child = mChildHelper.getChildAt(i);
            final ViewHolder viewHolder = getChildViewHolderInt(child);
            if (viewHolder.shouldIgnore()) {
                continue;
            }
            if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
                boolean wasHidden = viewHolder
                        .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                if (!wasHidden) {
                    flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                }
                final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                        mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                if (wasHidden) {
                    recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                } else {
                    mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                }
            }
        }
        // we don't process disappearing list because they may re-appear in post layout pass.
        clearOldPositions();
    } else {
        clearOldPositions();
    }
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    /**这里改变了mState.mLayoutStep的值**/
    mState.mLayoutStep = State.STEP_LAYOUT;
}

当我们的RecyclerView有动画时,才可能执行两个if语句里的代码。如果mState.mRunPredictiveAnimations为true,会先调用一次mLayout.onLayoutChildren(mRecycler, mState);。这个方法等下讲解。

dispatchLayoutStep2()
private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
    mState.mInPreLayout = false;
    mLayout.onLayoutChildren(mRecycler, mState);
    mState.mStructureChanged = false;
    mPendingSavedState = null;
    // onLayoutChildren may have caused client code to disable item animations; re-check
    mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
    /**这里改变了mState.mLayoutStep的值**/
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
}

dispatchLayoutStep2()貌似很简单,没做什么事情。其实不然,它调用了mLayout.onLayoutChildren(mRecycler, mState);方法,这个方法才是重头戏。RecyclerView将子View的绘制交给了LayoutManager去做,其实就是在onLayoutChildren这个方法里处理的。

onLayoutChildren方法在子类实现。那么来看看LinearLayoutManager里的onLayoutChildren方法。

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    ......
    //判断绘制方向,给mShouldReverseLayout赋值,默认是正向绘制,则mShouldReverseLayout是false
    resolveShouldLayoutReverse();
    final View focused = getFocusedChild();
    if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
            || mPendingSavedState != null) {
        mAnchorInfo.reset();
        mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
        // 计算锚点的位置和偏移量
        updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
        mAnchorInfo.mValid = true;
    } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                    >= mOrientationHelper.getEndAfterPadding()
            || mOrientationHelper.getDecoratedEnd(focused)
            <= mOrientationHelper.getStartAfterPadding())) {
        mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
    }
    ......
    if (mAnchorInfo.mLayoutFromEnd) {
        ......
    } else {
        // fill towards end
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtra = extraForEnd;
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;
        final int lastElement = mLayoutState.mCurrentPosition;
        if (mLayoutState.mAvailable > 0) {
            extraForStart += mLayoutState.mAvailable;
        }
        // fill towards start
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtra = extraForStart;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;
        if (mLayoutState.mAvailable > 0) {
            extraForEnd = mLayoutState.mAvailable;
            // start could not consume all it should. add more items towards end
            updateLayoutStateToFillEnd(lastElement, endOffset);
            mLayoutState.mExtra = extraForEnd;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
        }
    }
    ......
}

这个方法实在是复杂,即使删掉了部分代码还是有点长,但是我们只关心子view是如何绘制的。

这里有个锚点的概念。理解为要开始绘制的位置就可以了。

锚点和填充的关系.png

一开始我对这张图有点懵逼。其实抓住那个小红点就可以了,小红点就是所谓的锚点,也就是绘制的开始位置。第一种情况中,小红点在最底部,那么就从n-1的位置开始绘制子view。第二种情况中,小红点在顶部,从0开始绘制子view。

这张图就描述了onLayoutChildren里的主要工作。

updateAnchorInfoForLayout和updateLayoutStateToFillEnd等几个类似的方法都是对锚点位置的计算。那么哪个方法真正执行了绘制工作呢?答案是fill()方法。

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    ......
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    /**判断是否还有空间,循环执行layoutChunk方法**/
    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;
            remainingSpace -= layoutChunkResult.mConsumed;
        }
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
        if (stopOnFocusable && layoutChunkResult.mFocusable) {
            break;
        }
    }
    return start - layoutState.mAvailable;
}

继续找layoutChunk()方法:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    /**寻找要绘制的view,这个next方法可是相当重要的,里面包含RecyclerView的缓存逻辑,以后再分析吧**/
    View view = layoutState.next(recycler);
    if (view == null) {
        return;
    }
    LayoutParams params = (LayoutParams) view.getLayoutParams();
    /**将子view add到RecyclerView中**/
    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);
        }
    }

    /**对子veiw进行一次测量**/
    measureChildWithMargins(view, 0, 0);

    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);

    /**这四个变量是子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;
        }
    }
    /**对子view进行layout**/
    layoutDecoratedWithMargins(view, left, top, right, bottom);
   
    // 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进行measure和layout的地方。

这里总结下dispatchLayoutStep2()方法中做的事:

  1. 计算锚点,以锚点开始填充RecyclerView(其实就是执行fill方法)。

  2. 执行fill方法,判断RecyclerView是否还有空间,如果有,执行layoutChunk方法,直至填充满。

  3. layoutChunk方法中,寻找到当前要添加的子view,add到RecyclerView中。

  4. 对子view进行measure和layout。

至此,dispatchLayoutStep2()就结束了。

dispatchLayoutStep3()

onLayout中会调用dispatchLayoutStep3()方法。

private void dispatchLayoutStep3() {
    ......
    /**重置了mState.mLayoutStep**/
    mState.mLayoutStep = State.STEP_START;
    /**处理动画**/
    if (mState.mRunSimpleAnimations) {
        ......
    }
    mLayout.removeAndRecycleScrapInt(mRecycler);
    mState.mPreviousLayoutItemCount = mState.mItemCount;
    mDataSetHasChangedAfterLayout = false;
    mDispatchItemsChangedEvent = false;
    mState.mRunSimpleAnimations = false;
    mState.mRunPredictiveAnimations = false;
    mLayout.mRequestedSimpleAnimations = false;
    if (mRecycler.mChangedScrap != null) {
        mRecycler.mChangedScrap.clear();
    }
    if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
        mLayout.mPrefetchMaxCountObserved = 0;
        mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
        mRecycler.updateViewCacheSize();
    }
    /**表明layout过程结束**/
    mLayout.onLayoutCompleted(mState);
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    mViewInfoStore.clear();
    if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
        dispatchOnScrolled(0, 0);
    }
    recoverFocusFromState();
    resetFocusInfo();
}

其实dispatchLayoutStep3()就是做了一些收尾工作,将一些变量重置,处理下动画。

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

看到这么简单的方法真是开心。这里的逻辑确实很简单,还记得ItemDecoration吗?我们一般用这个来实现分割线效果,如果我们执行了addItemDecoration,这里RecyclerView的onDraw方法就帮我们把这些分割线绘制出来。

三、总结

1.RecyclerView是将绘制流程交给LayoutManager处理,如果没有设置不会绘制子View。

2.绘制是先确定锚点,以锚点开始,向上绘制,向下绘制,fill()至少会执行两次,如果绘制完还有剩余空间,则会再执行一次fill()方法。

3.fill方法中会根据剩余空间循环执行layoutChunk方法。

4.layoutChunk方法add了子view,并对子view进行了measure和layout。

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

推荐阅读更多精彩内容