likes
comments
collection
share

RecyclerView 静态布局实现过程解析:如何构建高性能的列表

作者站长头像
站长
· 阅读数 9

Recyclerview在日常开发中所使用的控件中绝对是顶流一般的存在,想嚼它这个想法一次两次了。在网上也看了很多关于Recyclerview源码解析的文章,大佬们写的都很深刻,但是对于像我们这种储备知识不足的小白读者来说,那感觉就像外地人坐上了黑车,你说咋走就咋走。

从标题上能看出来,一篇文章肯定是写不完的,我准备分为以下几章来逐步介绍:

我们把这5章给串起来就是Recyclerview完整使用下来的整个流程解析。

  • 第一章,因为Recyclerview即使他再强大他终究也是一个View,是ViewGroup你就肯定就要初始化,初始化就是指在没有任何交互场景下走的流程,所以也叫静态布局。

  • 第二章,因为Recyclerview是一个可滑动的控件,在静态布局完成后,他就要担起滑动的责任了,所以第二章介绍滑动时代码逻辑是怎么执行的。

  • 第三章,Recyclerview滑动逻辑了解清楚了之后,我们就可以开始着手探索Recyclerview他的child是从哪里来的,又要到哪儿去?这就涉及到Recyclerview的缓存逻辑了。

  • 第四章,我们前面三章把其实都是与探索androidViewGrouponlayout流程,然后我们就可以探索notifyItemInserted(int)notifyItemRemoved(int)notifyItemChanged(int)等引起布局变化的逻辑了。

  • 第五章,当我们有了第四章的基础之后我们再去探索他们引起布局变化时布局动画又是怎么执行的了。

测量流程

Recyclerview既然是一个View,那他的测量流程必然会走到onMeasure()

onMeasure

 @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        //如果没设置LayoutManager那就不用走测量和布局流程
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }

        //LinearLayoutManager isAutoMeasureEnabled() 默认是true
        if (mLayout.isAutoMeasureEnabled()) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);

            /**
             * This specific call should be considered deprecated and replaced with
             * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
             * break existing third party code but all documentation directs developers to not
             * override {@link LayoutManager#onMeasure(int, int)} when
             * {@link LayoutManager#isAutoMeasureEnabled()} returns true.
             */
            //其实这里从源码注释中可以看到这个地方已经废弃了,但是之所以没有删除是为了保证第三方库的稳定性
            //所以这里直接跳过
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

            //在这里我们判断给的宽高约束是否都是EXACTLY,如果是的话那就不用走复杂的测量逻辑了
            mLastAutoMeasureSkippedDueToExact =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            if (mLastAutoMeasureSkippedDueToExact || mAdapter == null) {
                return;
            }

            //这里mState.mLayoutStep的默认值就是State.STEP_START
            //但是呢,在静态布局的时候我们不用管这个流程,因为这里主要是
            //为了处理动画相关的逻辑,这里会在第四章重点介绍的
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }
          
            //将目前测量出来的大小传递给LayoutManager,因为最终复测layout children
            //的LayoutManager,所以他需要只要大小
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            //这里要重点关注,因为这里是真正执行layout的地方
            dispatchLayoutStep2();

            //因为dispatchLayoutStep2()执行了layout逻辑,所以Recyclerview已经被
            //children撑起来了,那就可以通过children获取RV的尺寸
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

            //如果layout还没测量出明确的尺寸,就需要第二次测量
            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);
            }

            mLastAutoMeasureNonExactMeasuredWidth = getMeasuredWidth();
            mLastAutoMeasureNonExactMeasuredHeight = getMeasuredHeight();
        } else {
           ...
        }
    }

为了对代码有全面的了解,我选择在代码中以注释的形式讲解,然后再对代码中的细节进行详细讲解,关于onMeasure的大体流程都在上方面以注释的形式进行解释了,从注释中我们可以看到我们需要重点关注的逻辑有dispatchLayoutStep1()dispatchLayoutStep2()

Recyclerview.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.mLayoutStep = State.STEP_LAYOUT;
}

从官方注释中能看到dispatchLayoutStep1()主要有四个职责:

  • 处理adapter发起的布局更新
  • 决定执行什么样的动画
  • 保存当前children的特定信息
  • 如果有必要就执行predictive动画

从注释上也看看出来这段逻辑和静态布局几乎没有关系,但是在这里把代码贴出来而且仅贴了最后一行,因为这里更新了mState.mLayoutStep的状态,在dispatchLayoutStep2()中会用到

Recyclerview.dispatchLayoutStep2()


private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    //因为在 dispatchLayoutStep1()中最后一行代码设置为State.STEP_LAYOUT,所以正常往下走
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    //这里主要是处理adapter 的增删改的逻辑
    mAdapterHelper.consumeUpdatesInOnePass();
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
    if (mPendingSavedState != null && mAdapter.canRestoreState()) {
        if (mPendingSavedState.mLayoutState != null) {
            mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState);
        }
        mPendingSavedState = null;
    }
    // Step 2: Run layout
    //标记当前已经不是预布局(dispatchLayoutStep1)阶段
    mState.mInPreLayout = false;
    //交给LayoutManager进行布局
    mLayout.onLayoutChildren(mRecycler, mState);

    mState.mStructureChanged = false;

    // onLayoutChildren may have caused client code to disable item animations; re-check
    //检查是否支持动画
    mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
    //更新布局阶段的状态
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
}

因为这章只讲静态布局,所以真正相关的逻辑只在mLayout.onLayoutChildren(mRecycler, mState),逻辑已经进入LM(LayoutManager)中了,那这里我们就以LLM(LinearLayoutManager)为例

LLM.onLayoutChildren()


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.
    // create layout state
    if (DEBUG) {
        Log.d(TAG, "is pre layout:" + state.isPreLayout());
    }
    if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
        if (state.getItemCount() == 0) {
            removeAndRecycleAllViews(recycler);
            return;
        }
    }
    if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
        mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
    }

    //创建一个LayoutState对象,这是一个Layout流程标志位统一管理对象
    ensureLayoutState();
    mLayoutState.mRecycle = false;
    // resolve layout direction
    //判断当前设置的布局方向,与构造函数中的reverseLayout参数有关
    resolveShouldLayoutReverse();

    final View focused = getFocusedChild();

    //第一次进来时 mAnchorInfo.mValid 是false
    if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
            || mPendingSavedState != null) {
        mAnchorInfo.reset();
        //mStackFromEnd的取值是根据自定义属性stackFromEnd的传值定的,默认是false,这里
        //我们一reverseLayout为false情况为准,所以mAnchorInfo.mLayoutFromEnd最终为false,
        //下面会用到
        mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
        // calculate anchor position and coordinate
        //更新AnchorInfo,默认情况下,我们肯定都是认为布局children都是从
        //RV的顶部然后然后布局到RV的地步,我们静态布局确实是这样的
        //其实这里做得主要逻辑就是找到第一个没被remove的child
        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 (DEBUG) {
        Log.d(TAG, "Anchor info:" + mAnchorInfo);
    }

    // LLM may decide to layout items for "extra" pixels to account for scrolling target,
    // caching or predictive animations.

    // 如果 mLastScrollDelta 大于等于 0,则说明当前的布局方向是向末尾(即底部)方向布局(即 LayoutState.LAYOUT_END),
    // 否则就是向起始(即顶部)方向布局(即 LayoutState.LAYOUT_START)
    mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
            ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
    mReusableIntPair[0] = 0;
    mReusableIntPair[1] = 0;
    //①计算布局开始和结束的位置
    //如果是从顶部往底部布局mReusableIntPair[0] = 0,mReusableIntPair[1] = RV.height - padding
    //反之mReusableIntPair[0]与mReusableIntPair[1]值互换
    //后面同列出这个逻辑的详细代码
    calculateExtraLayoutSpace(state, mReusableIntPair);
    //在根据padding和margin计算最终的开始与结束位置
    int extraForStart = Math.max(0, mReusableIntPair[0])
            + mOrientationHelper.getStartAfterPadding();
    int extraForEnd = Math.max(0, mReusableIntPair[1])
            + mOrientationHelper.getEndPadding();
    //这里我们讨论的是静态布局,而且在dispatchLayoutStep2中已经把isPreLayout置为false了
    //所以这里的if不成立
    if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION
            && mPendingScrollPositionOffset != INVALID_OFFSET) {
        // if the child is visible and we are going to move it around, we should layout
        // extra items in the opposite direction to make sure new items animate nicely
        // instead of just fading in
        final View existing = findViewByPosition(mPendingScrollPosition);
        if (existing != null) {
            final int current;
            final int upcomingOffset;
            if (mShouldReverseLayout) {
                current = mOrientationHelper.getEndAfterPadding()
                        - mOrientationHelper.getDecoratedEnd(existing);
                upcomingOffset = current - mPendingScrollPositionOffset;
            } else {
                current = mOrientationHelper.getDecoratedStart(existing)
                        - mOrientationHelper.getStartAfterPadding();
                upcomingOffset = mPendingScrollPositionOffset - current;
            }
            if (upcomingOffset > 0) {
                extraForStart += upcomingOffset;
            } else {
                extraForEnd -= upcomingOffset;
            }
        }
    }
    int startOffset;
    int endOffset;
    final int firstLayoutDirection;
    //根据mLayoutFromEnd和mShouldReverseLayout判断第一个child布局方向
    //因为mLayoutFromEnd和mShouldReverseLayout都是false,
    //所以firstLayoutDirection ==LayoutState.ITEM_DIRECTION_TAIL
    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();
    // noRecycleSpace not needed: recycling doesn't happen in below's fill
    // invocations because mScrollingOffset is set to SCROLLING_OFFSET_NaN
    mLayoutState.mNoRecycleSpace = 0;
    //上面因为mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;逻辑
    //所以这里是false
    if (mAnchorInfo.mLayoutFromEnd) {
        // fill towards start
        //将mAnchorInfo 布局信息设置给mLayoutState
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtraFillSpace = extraForStart;
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;
        final int firstElement = mLayoutState.mCurrentPosition;
        if (mLayoutState.mAvailable > 0) {
            extraForEnd += mLayoutState.mAvailable;
        }
        // fill towards end
        //再从anchorView反方向填充
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtraFillSpace = extraForEnd;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;

        if (mLayoutState.mAvailable > 0) {
            // end could not consume all. add more items towards start
            extraForStart = mLayoutState.mAvailable;
            updateLayoutStateToFillStart(firstElement, startOffset);
            mLayoutState.mExtraFillSpace = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
        }
    } else {
        // fill towards end
        //② 根据AnchorInfo来更新LayoutState的内容,主要同步布局方向,
        //从RV的什么位置开始布局(比如从RV y轴100的地方开始向底部布局)
        //还剩多少可用空间可以用来布局
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtraFillSpace = 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.mExtraFillSpace = 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.mExtraFillSpace = extraForEnd;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
        }
    }

    ...
    
    layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
    if (!state.isPreLayout()) {
        mOrientationHelper.onLayoutComplete();
    } else {
        mAnchorInfo.reset();
    }
    mLastStackFromEnd = mStackFromEnd;
    if (DEBUG) {
        validateChildOrder();
    }
}

正如官方注释所说,onLayoutChildren()主要有以下职责:

  1. 参数检查并找到anchorView
  2. 向前填充child
  3. 向后填充child
  4. XXXXXXX,我没懂....

懵逼了有没有,只是一个lauout流程,怎么又是找AnchorView,又从向前填充,又是向后填充!!!,正常情况我们都是一把梭,从头填充到尾然后打完收工.

在我们这个章节里其实就是找AnchorView没什么逻辑,但是找到AnchorView这个机制也不是为了静态布局合计的,更多的是为了Adapter增删改设计的,但是这里还是想讲一下,所以我们先看一下是怎么找到AnchorView,然后再说一下为什么要有AnchorView这个设计.

LLM.updateAnchorInfoForLayout()

private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
        AnchorInfo anchorInfo) {
        //这里应该和smoothScrooll相关,暂时不用管
    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 updateAnchorFromChildren(RecyclerView.Recycler recycler,
        RecyclerView.State state, AnchorInfo anchorInfo) {
    if (getChildCount() == 0) {
        return false;
    }
    //这里和FocusedChild相关,也不用管
    final View focused = getFocusedChild();
    if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
        anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
        return true;
    }
    if (mLastStackFromEnd != mStackFromEnd) {
        return false;
    }
    //① 主要看这里他是如何找到anchorView的
    View referenceChild =
            findReferenceChild(
                    recycler,
                    state,
                    anchorInfo.mLayoutFromEnd,
                    mStackFromEnd);
    
if (referenceChild != null) {
    anchorInfo.assignFromView(referenceChild, getPosition(referenceChild));
    // If all visible views are removed in 1 pass, reference child might be out of bounds.
    // If that is the case, offset it back to 0 so that we use these pre-layout children.
    //如果不是preLayout且支持Predictive动画
    //就会执行以下逻辑
    if (!state.isPreLayout() && supportsPredictiveItemAnimations()) {
        // validate this child is at least partially visible. if not, offset it to start
        final int childStart = mOrientationHelper.getDecoratedStart(referenceChild);
        final int childEnd = mOrientationHelper.getDecoratedEnd(referenceChild);
        final int boundsStart = mOrientationHelper.getStartAfterPadding();
        final int boundsEnd = mOrientationHelper.getEndAfterPadding();
        // b/148869110: usually if childStart >= boundsEnd the child is out of
        // bounds, except if the child is 0 pixels!
        boolean outOfBoundsBefore = childEnd <= boundsStart && childStart < boundsStart;
        boolean outOfBoundsAfter = childStart >= boundsEnd && childEnd > boundsEnd;
        //判断当前找到的view是否在RV的屏幕外部
        if (outOfBoundsBefore || outOfBoundsAfter) {
            anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd ? boundsEnd : boundsStart;
        }
    }

        return true;
    }
    return false;
}




View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state,
        boolean layoutFromEnd, boolean traverseChildrenInReverseOrder) {
    ensureLayoutState();

    // Determine which direction through the view children we are going iterate.
    int start = 0;
    int end = getChildCount();
    int diff = 1;
    if (traverseChildrenInReverseOrder) {
        start = getChildCount() - 1;
        end = -1;
        diff = -1;
    }

    int itemCount = state.getItemCount();

    final int boundsStart = mOrientationHelper.getStartAfterPadding();
    final int boundsEnd = mOrientationHelper.getEndAfterPadding();

    View invalidMatch = null;
    View bestFirstFind = null;
    View bestSecondFind = null;
    //上面的代码不是很重要,都是为了这里的for循环做铺垫的,主要就是
    //如果你是从顶部向底部布局,那么for循环就是从0 开始,for循环每次+diff(1)
    //如果你是从底部布局,那么for循环就是从childCount-1开始,每次+diff(-1)
    //重要逻辑在这个for循环中:这里面的逻辑就是找到第一个没有被remove的child
    //如果没有找到就在第二优先级中寻找,然后第三优先级
    for (int i = start; i != end; i += diff) {
        final View view = getChildAt(i);
        final int position = getPosition(view);
        final int childStart = mOrientationHelper.getDecoratedStart(view);
        final int childEnd = mOrientationHelper.getDecoratedEnd(view);
        if (position >= 0 && position < itemCount) {
            if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
                if (invalidMatch == null) {
                    invalidMatch = view; // removed item, least preferred
                }
            } else {
                // b/148869110: usually if childStart >= boundsEnd the child is out of
                // bounds, except if the child is 0 pixels!
                boolean outOfBoundsBefore = childEnd <= boundsStart && childStart < boundsStart;
                boolean outOfBoundsAfter = childStart >= boundsEnd && childEnd > boundsEnd;
                if (outOfBoundsBefore || outOfBoundsAfter) {
                    // The item is out of bounds.
                    // We want to find the items closest to the in bounds items and because we
                    // are always going through the items linearly, the 2 items we want are the
                    // last out of bounds item on the side we start searching on, and the first
                    // out of bounds item on the side we are ending on.  The side that we are
                    // ending on ultimately takes priority because we want items later in the
                    // layout to move forward if no in bounds anchors are found.
                    if (layoutFromEnd) {
                        if (outOfBoundsAfter) {
                            bestFirstFind = view;
                        } else if (bestSecondFind == null) {
                            bestSecondFind = view;
                        }
                    } else {
                        if (outOfBoundsBefore) {
                            bestFirstFind = view;
                        } else if (bestSecondFind == null) {
                            bestSecondFind = view;
                        }
                    }
                } else {
                    // We found an in bounds item, greedily return it.
                    return view;
                }
            }
        }
    }
    // We didn't find an in bounds item so we will settle for an item in this order:
    // 1. bestSecondFind
    // 2. bestFirstFind
    // 3. invalidMatch
    return bestSecondFind != null ? bestSecondFind :
            (bestFirstFind != null ? bestFirstFind : invalidMatch);
}




简单概括就是下面这张图:

RecyclerView 静态布局实现过程解析:如何构建高性能的列表

为什么要找到找AnchorView呢?我们假设一个场景:

在一个高度为100dp的RV中,他的每个item的高度是20dp,现在因为增/删操作我我们的anchorView的坐标在(0,40)

现在有了AnchorView的坐标了,那Recyclerview是怎么使用的呢?他将layout流程分为两步,第一步:从AnchorView向顶部开始布局,第二步:从AnchorView从底部开始布局,一上一下就把整个屏幕布局满了.

LLM.calculateExtraLayoutSpace()

protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
        @NonNull int[] extraLayoutSpace) {
    int extraLayoutSpaceStart = 0;
    int extraLayoutSpaceEnd = 0;

    // If calculateExtraLayoutSpace is not overridden, call the
    // deprecated getExtraLayoutSpace for backwards compatibility
    //①获取总共可布局空间的大小
    @SuppressWarnings("deprecation")
    int extraScrollSpace = getExtraLayoutSpace(state);
    if (mLayoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
    //如果是向上布局,extraLayoutSpaceStart的值就是extraScrollSpace
        extraLayoutSpaceStart = extraScrollSpace;
    } else {
        extraLayoutSpaceEnd = extraScrollSpace;
    }

    extraLayoutSpace[0] = extraLayoutSpaceStart;
    extraLayoutSpace[1] = extraLayoutSpaceEnd;
}



//获取RV总共可布局空间大小
protected int getExtraLayoutSpace(RecyclerView.State state) {
    if (state.hasTargetScrollPosition()) {
        //① 这里我们以LLM 的垂直布局为例
        return mOrientationHelper.getTotalSpace();
    } else {
        return 0;
    }
}



@Override
public int getTotalSpace() {
//通过RV的高度减去paddingVertical
    return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
            - mLayoutManager.getPaddingBottom();
}


从上面的代码可以看出calculateExtraLayoutSpace()他真的只是计算可布局的空间大小.其实最重要的就是layout流程,但是要layout就必须要找到AnchorView,现在我们有AnchorView我们就可以开始看layout流程了,所有layout流程都是从fill()开始的.

LLM.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);
    }
    //计算总共有多少空间可以用来摆放child
    int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
    //一个用来保存每次布局一个child的结果类,比如一个child消费了多少空间
    //是否应该真实的计算这个child消费的空间(预布局的时候有些child虽然消费了空间,
    // 但是不应该不参与真正的空间剩余空间的计算)
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    //只要还有空间和item就进行布局layoutchunk
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        //重置上一次布局child的结果
        layoutChunkResult.resetInternal();
        if (RecyclerView.VERBOSE_TRACING) {
            TraceCompat.beginSection("LLM LayoutChunk");
        }
        //①这里是真正layout child的逻辑
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        if (RecyclerView.VERBOSE_TRACING) {
            TraceCompat.endSection();
        }
        if (layoutChunkResult.mFinished) {
            break;
        }
        //layoutState.mLayoutDirection的值是 1或者-1 所以这里是 乘法
        //如果是从顶部往底部填充,当前填充的是第三个child 且每个高度是10dp,那么layoutState.mOffset的值
        //就是上次填充时的偏移量 + 这次填充child的高度
        //如果是从底部往顶部填充,那就是次填充时的偏移量 - 这次填充child的高度
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
        /**
         * Consume the available space if:
         * * layoutChunk did not request to be ignored
         * * OR we are laying out scrap children
         * * OR we are not doing pre-layout
         */
        //判断是否要真正的消费当前child参与布局所消费的高度
        //从判断条件中可以看到预布局和这个有关,不过预布局等后面几章会详细说的
        //这里就是同步目前还剩多少空间可以用来布局
        if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
                || !state.isPreLayout()) {
            layoutState.mAvailable -= layoutChunkResult.mConsumed;
            // we keep a separate remaining space because mAvailable is important for recycling
            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;
        }
    }
    if (DEBUG) {
        validateChildOrder();
    }
    return start - layoutState.mAvailable;
}

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    //这里创建一个childView(暂时理解过创建一个child,里面涉及从缓存中获取child)
    //但是我们这张不考虑缓存的逻辑,所以这里直接认定为创建了一个child,后面章节会
    //详细的介绍这里的
    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;
    }
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
    //这里就是判断是把这个child添加到头部还是尾部(由LLM构造函数参数决定)
    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宽高,这里的测量是LLM封装的一个测量宽高并加上margin的结果
    measureChildWithMargins(view, 0, 0);
    //记录当前child 消费了多少空间
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
    int left, top, right, bottom;
    //从这里看到定义了left, top, right, bottom,就知道肯定是
    //在计算当前child应该摆放在屏幕的什么坐标上了
    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.
    //进行布局
    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();
}

用伪代码来概括fill()就是

fun fill(){
    var couldLayoutSpace = layoutState.mAvailable + layoutState.mExtraFillSpace
    while (couldLayoutSpace >0 && curLayoutChildPosition < adapter.itemCount - 1){
        
        val layoutChunkResult = layoutChunk()
        couldLayoutSpace -= layoutChunkResult.consumed
    }
}

fun layoutChunk(){
    val childView = getChildView()
    measureChild(childView)
    layoutChild(child)
}

这篇文章我尽可能的做到每行代码都讲解,但是有很多代码是涉及后面篇章需要介绍的,所以这里就没有过多的介绍,我的对阅读源码的理解是不能贪多,最好的方式是先理解了概念一,再理解概念二,如果在概念一还没完全理解的情况,就尝试理解概念二,概念二要是理解通了还好,要是没理解好,还会打击理解概念一的信心.如果上面有什么不对的地方烦请大佬们能够指出,如果有没介绍清楚的地方,可以提出来,只有我把Recyclerview完全讲通了,才代表我真正的消化了.