RecyclerView源码学习笔记一(绘制流程)

本文参考了【进阶】RecyclerView源码解析(一)——绘制流程,有不少地方我是直接照搬过来的,侵权立删。

源码版本基于com.android.support:recyclerview-v7:27.1.1

RecyclerView的源码非常复杂,再加上一些辅助类其代码量超过了2W行,这也导致很多人望而却步,但是作为Android体系中一个非常重要的组件,学习其源码对于我们了解RecyclerView是如何运行的有很大的帮助。由于RecyclerView的源码实在太多,所以我们就从主要的流程开始,毕竟RecyclerView也是直接继承自ViewGroup,我们都知道自定义ViewGroup其主要流程在于重写onMeasure,onLayout以及onDraw三个方法,我们也先从这三个流程来入手。

onMesure

protected void onMeasure(int widthSpec, int heightSpec) {
    //没有设置LayoutManager走默认的measure流程
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    //这里LayoutManager的几个子类isAutoMeasureEnabled()方法默认为true
    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;
        }

        //mState.mLayoutStep默认值是State.STEP_START
        if (mState.mLayoutStep == State.STEP_START) {
            //measure前的一些准备工作
            dispatchLayoutStep1();
            //走完dispatchLayoutStep1()后mState.mLayoutStep是State.STEP_LAYOUT
        }
        // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
        // consistency
        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;
        //实际measure发生的地方,这里执行完后mState.mLayoutStep变为State.STEP_ANIMATIONS
        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.
        //判断是否需要执行二次测量,如果需要则再执行一次 dispatchLayoutStep2()
        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;
        }

        //代码省略......       

        startInterceptRequestLayout();
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        stopInterceptRequestLayout(false);
        mState.mInPreLayout = false; // clear
    }
}

整个measure的流程大致如上,中间删掉了一些代码,其实看源码我们没有必要去深究代码细节,只需要关注其整体流程即可。
我们从上往下逐个分:

//没有设置LayoutManager走默认的measure流程
if (mLayout == null) {
    defaultOnMeasure(widthSpec, heightSpec);     
    return;
}

mLayout就是我们为RecyclerView设置的LayoutManager,这里判断当我们没有设置LayoutManager时,走默认的测量。

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

//LayoutManager.chooseSize方法
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);
    }
}

chooseSize方法就是根据自身的测量模式(mode)来获取其相应的自身宽高,然后直接调用setMeasuredDimension方法设置自身宽高。由于这里没有进行child的测量,因此不难理解当我们没有设置LayoutManager时界面也就会出现什么都没有的情况。
然后判断mLayout.isAutoMeasureEnabled()。

//LinearLayoutManager
public boolean isAutoMeasureEnabled() {
    return true;
}

这里默认为true(GridLayoutManager和StaggerLayoutManager与此类似),进入内部流程。
接下来判断测量模式,如果是精确测量模式则什么也不做,直接返回,否则进入下一步

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

判断当前绘制步骤状态(默认是State.STEP_START,RecyclerView内部维护了一个叫State的类,便于管理RecyclerView相应的状态信息,这个后面再分析),执行相应操作。

//mState.mLayoutStep默认值是State.STEP_START
if (mState.mLayoutStep == State.STEP_START) {
    //measure前的一些准备工作
    dispatchLayoutStep1();
    //走完dispatchLayoutStep1()后mState.mLayoutStep是State.STEP_LAYOUT
}

//RecyclerView的一个内部类,管理RecyclerView的状态信息
public static class State {
    static final int STEP_START = 1;
    static final int STEP_LAYOUT = 1 << 1;
    static final int STEP_ANIMATIONS = 1 << 2;
    ......
    @LayoutState
    int mLayoutStep = STEP_START;
    ......
}

执行dispatchLayoutStep1()方法,从这个方法的注释就可以看到,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() {
    mState.assertLayoutStep(State.STEP_START);
    fillRemainingScrollValues(mState);
    mState.mIsMeasuring = false;
    startInterceptRequestLayout();
    mViewInfoStore.clear();
    onEnterLayoutOrScroll();
    processAdapterUpdatesAndSetAnimationFlags();
    saveFocusInfo();
    mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
    mItemsAddedOrRemoved = mItemsChanged = false;
    mState.mInPreLayout = mState.mRunPredictiveAnimations;
    mState.mItemCount = mAdapter.getItemCount();
    findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);

    if (mState.mRunSimpleAnimations) {
        // Step 0: Find out where all non-removed items are, pre-layout
        int count = mChildHelper.getChildCount();
        ......
    }
    if (mState.mRunPredictiveAnimations) {
        // Step 1: run prelayout: This will use the old positions of items. The layout manager
        // is expected to layout everything, even removed items (though not to add removed
        // items back to the container). This gives the pre-layout position of APPEARING views
        // which come into existence as part of the real layout.

        // Save old positions so that LayoutManager can run its mapping logic.
        ......
        // we don't process disappearing list because they may re-appear in post layout pass.
        clearOldPositions();
    } else {
        clearOldPositions();
    }
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    //方法执行完后,将mState.mLayoutStep设置为State.STEP_LAYOUT,便于下一步操作
    mState.mLayoutStep = State.STEP_LAYOUT;
}

执行dispatchLayoutStep2(),这里也是真正执行子view布局的地方,可以看到RecyclerView将layout工作转交给了LayoutManager。

/**
 * The second layout step where we do the actual layout of the views for the final state.
 * This step might be run multiple times if necessary (e.g. measure).
 */
private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    //获取子view的数量,这里调用了adapter的getItemCount()方法。
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout
    mState.mInPreLayout = false;
    //实际执行子view布局的地方,这里是将measure工作转交给LayoutManager。
    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;
    //测量工作完成后,修改State的layoutStep为State.STEP_ANIMATIONS,
    //为后续子view显示或退出显示动画做准备
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
}

可以看到,实际执行子元素布局的工作转交给了LayoutManager,其具体实现在LayoutManager的子类中,这里以LinearLayoutManager为例来分析。

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.
    //填充子view,从锚点往上和从锚点往下
    // 2) fill towards start, stacking from bottom
    // 3) fill towards end, stacking from top
    //RecyclerView滑动时填充需要的布局
    // 4) scroll to fulfill requirements like stack from bottom.
    // create layout state

    //代码省略......

    ensureLayoutState();
    mLayoutState.mRecycle = false;
    //判断绘制方向,默认是垂直布局或者LayoutDirection是从左往右
    // resolve layout direction
    resolveShouldLayoutReverse();

    final View focused = getFocusedChild();
    //如果锚点信息无效则重置锚点信息
    if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
            || mPendingSavedState != null) {
        mAnchorInfo.reset();
        mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
        // calculate anchor position and coordinate
        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));
    }

    //代码省略......

    final int firstLayoutDirection;
    //判断布局方向,mAnchorInfo.mLayoutFromEnd默认为false
    if (mAnchorInfo.mLayoutFromEnd) {
        firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
                : LayoutState.ITEM_DIRECTION_HEAD;
    } else {
        firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
                : LayoutState.ITEM_DIRECTION_TAIL;
    }

    onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
    detachAndScrapAttachedViews(recycler);
    mLayoutState.mInfinite = resolveIsInfinite();
    mLayoutState.mIsPreLayout = state.isPreLayout();

    if (mAnchorInfo.mLayoutFromEnd) {
        //如果是倒着布局的话
        // fill towards start
        //从锚点往上布局
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtra = extraForStart;
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;
        final int firstElement = mLayoutState.mCurrentPosition;
        if (mLayoutState.mAvailable > 0) {
            extraForEnd += mLayoutState.mAvailable;
        }
        // fill towards end
        //从锚点往下布局
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtra = extraForEnd;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;

        //如果还有多余的填充像素数,再次运行fill方法
        if (mLayoutState.mAvailable > 0) {
            // end could not consume all. add more items towards start
            extraForStart = mLayoutState.mAvailable;
            updateLayoutStateToFillStart(firstElement, startOffset);
            mLayoutState.mExtra = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
        }
    } 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;

        //与上面类似,如果有多余的绘制,则再次调用fill方法
        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;
        }
    }

    // changes may cause gaps on the UI, try to fix them.
    // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
    // changed
    //这里是修复由于子元素滑动可能会导致出现的一些空白
    if (getChildCount() > 0) {
        // because layout from end may be changed by scroll to position
        // we re-calculate it.
        // find which side we should check for gaps.
        if (mShouldReverseLayout ^ mStackFromEnd) {
            int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
            startOffset += fixOffset;
            endOffset += fixOffset;
            fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
            startOffset += fixOffset;
            endOffset += fixOffset;
        } else {
            int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
            startOffset += fixOffset;
            endOffset += fixOffset;
            fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
            startOffset += fixOffset;
            endOffset += fixOffset;
        }
    }

    //完成后重置相关的一些参数
    layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
    if (!state.isPreLayout()) {
        mOrientationHelper.onLayoutComplete();
    } else {
        mAnchorInfo.reset();
    }
    mLastStackFromEnd = mStackFromEnd;
    //代码省略......
}

子元素具体的布局是在fill方法中。

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
         RecyclerView.State state, boolean stopOnFocusable) {
    // max offset we should set is mFastScroll + available
    final int start = layoutState.mAvailable;
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        // TODO ugly bug fix. should not happen
        if (layoutState.mAvailable < 0) {
            layoutState.mScrollingOffset += layoutState.mAvailable;
        }
        recycleByLayoutState(recycler, layoutState);
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        if (VERBOSE_TRACING) {
            TraceCompat.beginSection("LLM LayoutChunk");
        }
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        //代码省略......
    }
    //代码省略......
    return start - layoutState.mAvailable;
}

具体的逻辑都在layoutChunk(recycler, state, layoutState, layoutChunkResult)方法里面。

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;
    }
    LayoutParams params = (LayoutParams) view.getLayoutParams();
    //根据view的状态执行对应的addview方法
    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);
        }
    }
    //测量从LayoutState中取出的子view
    measureChildWithMargins(view, 0, 0);
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
    //计算left, top, right, bottom;
    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);
    //代码省略......
}

首先看下这行代码View view = layoutState.next(recycler);,这里非常重要。

View next(RecyclerView.Recycler recycler) {
    //在第一次加载RecyclerView的时候mScrapList为null,所以暂时不考虑
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    //获取view,这里使用了缓存策略
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

继续看recycler.getViewForPosition(mCurrentPosition);这个方法。

public View getViewForPosition(int position) {
    return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
    //代码省略......
    ViewHolder holder = null;
    //省略掉从缓存中获取view的代码......
    //代码省略......
        if (holder == null) {
            //代码省略......
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            //代码省略......
        }
    }
    //代码省略......
    return holder;
}

这里获取view最终调用ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)方法,并在里面进行了一堆判断,主要是判断scrap、cache以及RecyclerViewPool中是否有缓存,如果这三个缓存中都没有缓存的view,最后才调用adapter的createViewHolder方法创建一个viewholder,在createViewHolder方法中最终会调用我们自己实现的onCreateViewHolder方法。注意RecyclerView这里缓存的是ViewHolder,这也是RecyclerView与ListView不同的地方之一。

View view = layoutState.next(recycler);的分析暂时告一段落,沿着layoutChunk方法继续往下分析。

LayoutParams params = (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);
    }
}

这里就是将我们通过layoutState.next(recycler)获取的view添加到RecyclerView中,继续往下。

//测量获取的子view
//LinearLayoutManager->layoutChunk
measureChildWithMargins(view, 0, 0);

//RecyclerView->LayoutManager->measureChildWithMargins
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();

    //这里获取Itemdecoration里面设置的水平方向和竖直方向的偏移量
    final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
    widthUsed += insets.left + insets.right;
    heightUsed += insets.top + insets.bottom;

    //获取子view的measureSpec
    final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
            getPaddingLeft() + getPaddingRight()
                    + lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
            canScrollHorizontally());
    final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
            getPaddingTop() + getPaddingBottom()
                    + lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
            canScrollVertically());
    if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
        //调用child.measure方法测量子view自身宽高
        child.measure(widthSpec, heightSpec);
    }
}

这个方法主要是获取childView上下左右的偏移量以及完成childView自身的测量。这里分析下mRecyclerView.getItemDecorInsetsForChild(child);方法。

Rect getItemDecorInsetsForChild(View child) {
    //代码省略......
    final Rect insets = lp.mDecorInsets;
    insets.set(0, 0, 0, 0);
    //mItemDecorations是保存ItemDecoration的集合,
    //我们给RecyclerView绘制分割线等诸多view的装饰效果都是通过继承ItemDecoration来实现的
    final int decorCount = mItemDecorations.size();
    for (int i = 0; i < decorCount; i++) {
        mTempRect.set(0, 0, 0, 0);
        //调用ItemDecoration的getItemOffsets方法来获取相应的view偏移量
        mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
        insets.left += mTempRect.left;
        insets.top += mTempRect.top;
        insets.right += mTempRect.right;
        insets.bottom += mTempRect.bottom;
    }
    //代码省略......
    return insets;
}

这个方法里面循环调用了ItemDecoration的getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)方法(RecyclerView可以设置多个ItemDecoration),并将我们设置的left,top,right,bottom偏移量设置到outRect中去,最后得到RecyclerView设置的所有ItemDecoration的left,top,right,bottom偏移量。这个方法我们在继承ItemDecoration的时候需要我们自己去实现,这里简单说下偏移量,用一张图片来表示:

image.png

分析完了measureChildWithMargins后,继续往下。

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

这里是设置RecyclerView中各个子view应该布局的位置(left,top,right,bottom),设置完了后调用layoutDecoratedWithMargins(view, left, top, right, bottom);开始布局子view。

LinearLayoutManager->layoutChunk->layoutDecoratedWithMargins
layoutDecoratedWithMargins(view, left, top, right, bottom);

RecyclerView->LayoutManager->layoutDecoratedWithMargins
public void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    final Rect insets = lp.mDecorInsets;
    child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
            right - insets.right - lp.rightMargin,
            bottom - insets.bottom - lp.bottomMargin);
}

可以看到,在layoutDecoratedWithMargins方法中,子view的布局位置是由left,top,right,bottom和ItemDecoration设置的偏移量共同决定的。至此onMeasure的流程就分析完了,接下来看下onLayout方法。

onLayout

在分析onMeasure的时候有几个问题:

  • 1、为什么当不设置LayoutManager时,界面什么都没有?
    答:因为RecyclerView把子view的测量以及布局交给了LayoutManager来完成,当我们不设置LayoutManager时,RecyclerView无法完成子view的测量和布局,自然也就无法显示到界面上。
  • 2、当我们给RecyclerView设置精确宽高度时,onMeasure不也是直接就返回,没有经过LayoutManager的测量和布局吗,为什么最后能显示子view呢?
    答:当给RecyclerView设置精确宽高值时,LayoutManager对子view的测量和布局工作是在onLayout方法中完成的。这也是接下来将要分析的部分。
  • 3、在执行完dispatchLayoutStep2();方法后,设置了mState.mLayoutStep = State.STEP_ANIMATIONS;,这个STEP_ANIMATIONS又是在哪里调用的呢?
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    dispatchLayout();
    TraceCompat.endSection();
    mFirstLayoutComplete = true;
}

void dispatchLayout() {
    //代码省略......
    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方法中,仍然会判断State的mLayoutStep这个变量,当给RecyclerView设置精确宽高值是,在onMeasure方法中直接返回,跳过了接下来的步骤,因此mLayoutStep仍然是其初始值,也就是State.STEP_START,因此条件成立并执行dispatchLayoutStep1();dispatchLayoutStep2();完成子view的测量和布局。
这里还有一个判断,也就是当我们在布局的时候改变了RecyclerView的宽高值,也会调用dispatchLayoutStep2();方法重新测量和布局。
在dispatchLayout()方法的最后调用了dispatchLayoutStep3();,这里就可以回答上面的第三个问题,也就是STEP_ANIMATIONS就是在这个方法中判断的。根据注释,我们可以看出,这个方法主要是保存子view的一些动画以及做一些相关的清理工作。

private void dispatchLayoutStep3() {
    //这里有一个前提就是mLayoutStep必须是State.STEP_ANIMATIONS,
    //也就是执行完了dispatchLayoutStep2()后该方法才能继续往下走
    mState.assertLayoutStep(State.STEP_ANIMATIONS);
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    //这里将mLayoutStep又重置为了State.STEP_START
    mState.mLayoutStep = State.STEP_START;
    //如果设置了ItemAnimator,这里判断成立,RecyclerView预置了默认的ItemAnimator
    if (mState.mRunSimpleAnimations) {
        // Step 3: Find out where things are now, and process change animations.
        // traverse list in reverse because we may call animateChange in the loop which may
        // remove the target view holder.
        //循环保存ItemAnimator中设置的动画信息,如果有设置的话,RecyclerView中预置了默认动画
        for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
            ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
            //代码省略......
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPostLayoutInformation(mState, holder);
            ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
            if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
                final boolean oldDisappearing = mViewInfoStore.isDisappearing(
                        oldChangeViewHolder);
                final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
                if (oldDisappearing && oldChangeViewHolder == holder) {
                    // run disappear animation instead of change
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                } else {
                    final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
                            oldChangeViewHolder);
                    // we add and remove so that any post info is merged.
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                    ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
                    if (preInfo == null) {
                        handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
                    } else {
                        animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
                                oldDisappearing, newDisappearing);
                    }
                }
            } else {
                mViewInfoStore.addToPostLayout(holder, animationInfo);
            }
        }

        // Step 4: Process view info lists and trigger animations
        //执行ItemAnimator中设置的动画
        mViewInfoStore.process(mViewInfoProcessCallback);
    }

    //layout完成后,执行一些清理工作
    mLayout.removeAndRecycleScrapInt(mRecycler);
    mState.mPreviousLayoutItemCount = mState.mItemCount;
    mDataSetHasChangedAfterLayout = false;
    mDispatchItemsChangedEvent = false;
    mState.mRunSimpleAnimations = false;
    //代码省略......
}

这里看下mViewInfoStore.process(mViewInfoProcessCallback);这个方法。

mViewInfoStore.process(mViewInfoProcessCallback);

/**
 * The callback to convert view info diffs into animations.
 */
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
    new ViewInfoStore.ProcessCallback() {
       //代码省略......
        @Override
        public void processAppeared(ViewHolder viewHolder,
                                    ItemHolderInfo preInfo, ItemHolderInfo info) {
            animateAppearance(viewHolder, preInfo, info);
        }
       //代码省略......
    };

void animateAppearance(@NonNull ViewHolder itemHolder,
                       @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
    itemHolder.setIsRecyclable(false);
    //判断是否设置了动画,如果设置了,则执行动画
    if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
        postAnimationRunner();
    }
}

void postAnimationRunner() {
    if (!mPostedAnimatorRunner && mIsAttached) {
        //post动画执行
        ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
        mPostedAnimatorRunner = true;
    }
}

private Runnable mItemAnimatorRunner = new Runnable() {
    @Override
    public void run() {
        if (mItemAnimator != null) {
            //调用ItemAnimator的runPendingAnimations()方法执行动画,该方法需要我们自己实现
            mItemAnimator.runPendingAnimations();
        }
        mPostedAnimatorRunner = false;
    }
};

可以看到,所有动画逻辑都封装在了mViewInfoProcessCallback中,这个callback是ViewInfoStore类的一个内部接口,里面封装了各种动画行为。

interface ProcessCallback {
    void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
                            @Nullable ItemHolderInfo postInfo);
    void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
                         ItemHolderInfo postInfo);
    void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
                           @NonNull ItemHolderInfo postInfo);
    void unused(ViewHolder holder);
}


void process(ProcessCallback callback) {
    for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
        final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
        final InfoRecord record = mLayoutHolderMap.removeAt(index);
        if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
            // Appeared then disappeared. Not useful for animations.
            callback.unused(viewHolder);
        } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
            // Set as "disappeared" by the LayoutManager (addDisappearingView)
            if (record.preInfo == null) {
                // similar to appear disappear but happened between different layout passes.
                // this can happen when the layout manager is using auto-measure
                callback.unused(viewHolder);
            } else {
                callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
            }
        } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
            // Appeared in the layout but not in the adapter (e.g. entered the viewport)
            callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
            // Persistent in both passes. Animate persistence
            callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_PRE) != 0) {
            // Was in pre-layout, never been added to post layout
            callback.processDisappeared(viewHolder, record.preInfo, null);
        } else if ((record.flags & FLAG_POST) != 0) {
            // Was not in pre-layout, been added to post layout
            callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
        } else if ((record.flags & FLAG_APPEAR) != 0) {
            // Scrap view. RecyclerView will handle removing/recycling this.
        } else if (DEBUG) {
            throw new IllegalStateException("record without any reasonable flag combination:/");
        }
        InfoRecord.recycle(record);
    }
}

mViewInfoStore.process(mViewInfoProcessCallback);方法内部就是各种判断,并调用相应的动画执行。至此,onLayout方法的分析也告一段落,我们接着分析onDraw方法。

onDraw

在分析onDraw之前,首先了解下View的绘制流程。View的draw方法太长了,这里就不拿出来分析,我们只了解下流程,通过其源码我们可以得出大致的流程:

draw -> drawbackground(绘制背景,不能重写) -> onDraw(绘制自身) -> dispatchDraw(绘制子view) 
-> onDrawForeground(绘制前景) -> drawDefaultFocusHighlight(绘制其他)

RecyclerView继承了ViewGroup,内部重写了draw,onDraw这两个方法,我们就来分析下:


public void draw(Canvas c) {
    //调用父类的draw方法绘制自身
    //这里会依次调用上面所说的流程,完成绘制
    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.
    //判断是否需要重绘
    boolean needsInvalidate = false;
    if (mLeftGlow != null && !mLeftGlow.isFinished()) {
        //......
        needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
        c.restoreToCount(restore);
    }
    if (mTopGlow != null && !mTopGlow.isFinished()) {
        //......
    }
    if (mRightGlow != null && !mRightGlow.isFinished()) {
        //......
    }
    if (mBottomGlow != null && !mBottomGlow.isFinished()) {
        //......
    }

    // If some views are animating, ItemDecorators are likely to move/change with them.
    // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's
    // display lists are not invalidated.
    //如果有子view的动画还在执行,则需要重绘
    if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0
            && mItemAnimator.isRunning()) {
        needsInvalidate = true;
    }

    //如果需要重绘,发送重绘信息
    if (needsInvalidate) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
}

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

先看下draw方法,首先调用super.draw(canvas c),完成自身的绘制(这里会调用onDraw方法,完成divider的绘制);然后调用ItemDecoration的onDrawOver(需要我们自己实现)来绘制子view的悬浮效果,最后进行一些列判断,判断是否需要重绘。
接着看onDraw方法,这里首先调用super.onDraw(c);来绘制。然后调用ItemDecoration的onDraw方法来绘制分割线等效果,这个onDraw也是需要我们自己去实现的。

至此RecyclerView的整个绘制流程就分析完了,由于这里只侧重整体的流程,有很多详细的地方没有分析到,如有兴趣可自行分析细节。总结下几个关键点:

1、RecyclerView将子view的measure工作交给了LayoutManager来完成的,如果没有设置LayoutManager,则列表不会有任何显示。
2、RecyclerView的子view绘制分为正序绘制和倒序绘制。首先是确定锚点,然后由锚点分别向两边绘制,因此fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable)方法至少会执行两次,如果绘制完了还有剩余空间,则继续执行fill方法。
3、fill方法的核心逻辑在于layoutChunk方法中,layoutChunk方法中首先会通过layoutState.next(recycler)获取view,首先是从scrap、cache以及RecyclerViewPool等缓存中获取,如果这些缓存中都没有,则调用adapter的createViewHolder方法创建一个ViewHolder
4、如果没有为RecyclerView设置精确宽高值,则onMeasure方法会完成子view的测量以及布局(如有设置ItemDecoration,则会将ItemDecoration的getItemOffsets方法中设置的left,top,right,bottom偏移量也算在内),在onLayout方法中仅仅只需要完成一些清理工作以及子view进出动画的处理工作;如果设置了精确的宽高值,则对子view的测量以及布局工作会在onLayout中完成。
5、RecyclerView重写了draw和onDraw方法,如果设置了ItemDecoration,并重写了onDrawOver以及onDraw方法,则在draw方法中会调用ItemDecoration的onDrawOver绘制悬浮效果;在onDraw方法中调用ItemDecoration的onDraw方法绘制子view之间的分割线以及其他装饰效果。

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