美文网首页
RecyclerView 静态布局实现过程解析:如何构建高性能的

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

作者: 艾瑞败类 | 来源:发表于2023-05-03 13:20 被阅读0次

    作者:maxcion

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

    测量流程

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

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

    为什么要找到找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完全讲通了,才代表我真正的消化了.

    相关文章

      网友评论

          本文标题:RecyclerView 静态布局实现过程解析:如何构建高性能的

          本文链接:https://www.haomeiwen.com/subject/timjjdtx.html