美文网首页
RecyclerView的layoutManager

RecyclerView的layoutManager

作者: freelifes | 来源:发表于2022-03-19 14:48 被阅读0次

    Layoutmanager负责对recyclerview中的item, measure和layout, 也决定着回收那些不可见的item。支持list,grids和staggered grids的集合,本文主要分析下Linear和Grid的measure和layout过程。

    LinearLayoutManager-onLayoutChildren

    如果supportsPredictiveItemAnimations返回true, onLayoutchildren将会被执行2次。第一次pre-layout允许记录item的位置,即使这些item被移除,从scrap返回也会被重新放置,帮助计算其他的item位置。第二次是real-layout, 这次仅仅non-removed的views会被使用。如果一个view在pre-layout,但不在real-layout阶段,就会执行DISAPPEARING。看下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.
            // 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
            ensureLayoutState();
            mLayoutState.mRecycle = false;
            // resolve layout direction
            resolveShouldLayoutReverse();
    
            final View focused = getFocusedChild();
            if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.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));
            }
            // LLM may decide to layout items for "extra" pixels to account for scrolling target,
            // caching or predictive animations.
    
            mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
                    ? LinearLayoutManager.LayoutState.LAYOUT_END : LinearLayoutManager.LayoutState.LAYOUT_START;
            mReusableIntPair[0] = 0;
            mReusableIntPair[1] = 0;
            calculateExtraLayoutSpace(state, mReusableIntPair);
            int extraForStart = Math.max(0, mReusableIntPair[0])
                    + mOrientationHelper.getStartAfterPadding();
            int extraForEnd = Math.max(0, mReusableIntPair[1])
                    + mOrientationHelper.getEndPadding();
            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;
            if (mAnchorInfo.mLayoutFromEnd) {
                firstLayoutDirection = mShouldReverseLayout ? LinearLayoutManager.LayoutState.ITEM_DIRECTION_TAIL
                        : LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD;
            } else {
                firstLayoutDirection = mShouldReverseLayout ? LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD
                        : LinearLayoutManager.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;
            if (mAnchorInfo.mLayoutFromEnd) {
    
            } else {
                // fill towards end
                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;
        }
    

    1、更新anchorInfo

    anchorInfo 包含mPosition 开始填充child的位置。mCoordinate : 指定填充位置的偏移量。寻找anchorInfo有三种方式如下。
    1第一种:updateAnchorFromPendingData(state, anchorInfo) 其中mPosition对应mPendingScrollPosition,mCoordinate对应
    mPendingScrollPositionOffset 。这2个值都是scrollToPositionWithOffset(int, int)传递的。

     private boolean updateAnchorFromPendingData(RecyclerView.State state, LinearLayoutManager.AnchorInfo anchorInfo) {
            // 如果传入的偏移量无效
            if (mPendingScrollPositionOffset == INVALID_OFFSET) {
                View child = findViewByPosition(mPendingScrollPosition);
                if (child != null) {
                    final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
                    if (childSize > mOrientationHelper.getTotalSpace()) {
                        // item does not fit. fix depending on layout direction
                        //从padding分配值
                        anchorInfo.assignCoordinateFromPadding();
                        return true;
                    }
                    final int startGap = mOrientationHelper.getDecoratedStart(child)
                            - mOrientationHelper.getStartAfterPadding();
                    //如果目标child的start小于padding,coordinate取值为padding,
                    //意思之后从padding处开始放置view,保证view能够看见。
                    if (startGap < 0) {
                        anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding();
                        anchorInfo.mLayoutFromEnd = false;
                        return true;
                    }
                    //如果目标child的end大于endafterpadding,coordinater就取endAfterpadding
                    //意思之后 从endAfterpadding开始放置View。 保证view能够看见
                    final int endGap = mOrientationHelper.getEndAfterPadding()
                            - mOrientationHelper.getDecoratedEnd(child);
                    if (endGap < 0) {
                        anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding();
                        anchorInfo.mLayoutFromEnd = true;
                        return true;
                    }
                    //可见,就从原来child的start开始防止view,等于保持不动。
                    anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd
                            ? (mOrientationHelper.getDecoratedEnd(child) + mOrientationHelper
                            .getTotalSpaceChange())
                            : mOrientationHelper.getDecoratedStart(child);
                } else { // item is not visible.
                    if (getChildCount() > 0) {
                        // get position of any child, does not matter
                        int pos = getPosition(getChildAt(0));
                        anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos
                                == mShouldReverseLayout;
                    }
                    //找不到就从top(startpadding) 或者bottom(endafterpadding)开始布局。
                    anchorInfo.assignCoordinateFromPadding();
                }
                return true;
            }
            //如果mPendingScrollPositionOffset 不是无效,就从padding + 偏移量开始布局。
            anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
            // if this changes, we should update prepareForDrop as well
            if (mShouldReverseLayout) {
                anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding()
                        - mPendingScrollPositionOffset;
            } else {
                anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding()
                        + mPendingScrollPositionOffset;
            }
            return true;
        }
    

    上述代码详细写了寻找position和mCoordinate过程,目的就是保证的开始位置和偏移量。调用scrollToPositionWithOffset(32, Int.MIN_VALUE)只会创建需要的view。


    pendingAnchor.png

    如上图所以,调用scrollToPositionWithOffset(32, itemheight)
    anchorposition就等于32,而anchorcoordinate为itemheight,layout开始layoutend ,32->33->34->35->36->37 然后layoutstart ,31 ->30 如果mAvaiable>0 ,可以填充的空间大于0,再从lastelement处layoutend一次。
    第二种 通过指定view 确定从哪儿开始放置View。

       private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler,
                                                 RecyclerView.State state, LinearLayoutManager.AnchorInfo anchorInfo) {
            //找到一个view,如果这个不是remove, 没有超出边界,返回该View
            //view的top就是Coordinate  view的位置就是mPosition。
            View referenceChild = anchorInfo.mLayoutFromEnd
                    ? findReferenceChildClosestToEnd(recycler, state)
                    : findReferenceChildClosestToStart(recycler, state);
            if (referenceChild != null) {
                anchorInfo.assignFromView(referenceChild, getPosition(referenceChild));
                return true;
            }
            return false;
        }
    

    注意这个view是非remove的,view的top就是Coordinate ,view的位置就是mPosition。
    第三种方式 也是默认的方式。通过padding安排position和偏移量。

     anchorInfo.assignCoordinateFromPadding();
     anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0
    

    2 处理缓存

    detachAndScrapAttachedViews(recycler);
    这个方法就是清理屏幕上的holder,存到缓存策略中。比如notifydatasetchanged,所有的数据标记为无效,因此会存到pool中。而notifyItemchanged(index) index数据会被存放到changeScrap中,动画执行完成会存放到pool中,非index数据会被存放到attachScrap中,布局的时候直接从attachscrap中获取,onlayout完成后,会清空attachScrap中的holder。

    3 填充LayoutState信息

    1、mAvailable :child布局可用大小,默认都是recyclerview的大小。mAvailable = mOrientationHelper.getEndAfterPadding() - offset,offset默认为startPadding()
    2、mItemDirection : adapter的数据方向。mShouldReverseLayout,manager的第三个参数。
    3、mLayoutDirection : layout的布局方向。对于anchor的位置来说。LAYOUT_END 向下,LAYOUT_START 向上。
    4、mCurrentPosition :当前要放置view的位置。
    5、mOffset :每次layout下一个view的开始位置。
    6、mScrollingOffset : 处于滚动状态的时候,代表一个滚动距离,我们不用创建新得view。预读取的时候用到,比如: 手指向上滚动,滚动距离大于最后一个view的不可见距离(end),代表我们要add新的view了,否则不需要创建。

    4 填充layout

      int fill(RecyclerView.Recycler recycler, LinearLayoutManager.LayoutState layoutState,
                 RecyclerView.State state, boolean stopOnFocusable) {
            final int start = layoutState.mAvailable;
            if (layoutState.mScrollingOffset != LinearLayoutManager.LayoutState.SCROLLING_OFFSET_NaN) {
                //  滑动时候,因为Mavailable = 滑动距离 - 不要创建view的距离。所以可能为负数。
                // mAvailable =  5 -  12 = -7
                if (layoutState.mAvailable < 0) {
                    //mScrollingOffset = 12 -7 = 5(滑动距离) 虽然end方向的view不需要创建,头(start)可能需要回收。
                    //因为所有item高度可能各不同。
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
            int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
            LinearLayoutManager.LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
            while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
                layoutChunkResult.resetInternal();
                //measure和 layout
                layoutChunk(recycler, state, layoutState, layoutChunkResult);
                if (layoutChunkResult.mFinished) {
                    break;
                }
                layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
    
                //对于remove的item在真正布局的时候 layoutChunkResult.mIgnoreConsumed 为true
                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;
                }
                 // 滚动的时候回收item
                if (layoutState.mScrollingOffset != LinearLayoutManager.LayoutState.SCROLLING_OFFSET_NaN) {
                    layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                    //如果可填充空间小于0 ,则 不要创建view的距离 也应该减少。
                    if (layoutState.mAvailable < 0) {
                        layoutState.mScrollingOffset += layoutState.mAvailable;
                    }
                    recycleByLayoutState(recycler, layoutState);
                }
                if (stopOnFocusable && layoutChunkResult.mFocusable) {
                    break;
                }
            }
            return start - layoutState.mAvailable;
        }
    

    1、 layoutChunk(recycler, state, layoutState, layoutChunkResult); 从缓存策略中加载一个view, addView(view)。
    2、 measureChildWithMargins(view, 0, 0) 测量child,当width或者height支持滚动,则child的wrap_content,child的最终测量模式为MeasureSpec.makeMeasure(0,Measuspec.UNSPECIFIED),不支持滚动和viewgroup计算测量规则相同。 测量完成,child的left、right、top、bottom就能够确定了。 比如方向为LayoutState.LAYOUT_START也就是从anchor位置向上布局。layoutstate.mOffset就是每次布局start的位置,因此 top = layoutState.mOffset - result.mConsumed; bottom = layoutState.mOffset; 每一个item布局完成之后, 更新下次开始的位置layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;

    if (canScroll) {
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == RecyclerView.LayoutParams.MATCH_PARENT) {
                    switch (parentMode) {
                        case View.MeasureSpec.AT_MOST:
                        case View.MeasureSpec.EXACTLY:
                            resultSize = size;
                            resultMode = parentMode;
                            break;
                        case View.MeasureSpec.UNSPECIFIED:
                            resultSize = 0;
                            resultMode = View.MeasureSpec.UNSPECIFIED;
                            break;
                    }
                } else if (childDimension == RecyclerView.LayoutParams.WRAP_CONTENT) {
                    resultSize = 0;
                    resultMode = View.MeasureSpec.UNSPECIFIED;
                }
            }
    

    3、layoutDecoratedWithMargins(view, left, top, right, bottom); 此时的left,top等都是包含了mDecorInsets和child的margin了,因此真正layout的时候需要减去margin和decor。

         public void layoutDecoratedWithMargins(@NonNull 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);
            }
    

    5 scrollerBy回收holder

    recycleByLayoutState(recycler, layoutState); 回收holder, 手指向上滚动,布局方向就是layout_END 此时回收的start. 手指向下滚动,回收的就是end位置的holder。

    private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
                                         int noRecycleSpace) {
            final int childCount = getChildCount();
            final int limit = mOrientationHelper.getEnd() - scrollingOffset + noRecycleSpace;
            for (int i = childCount - 1; i >= 0; i--) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedStart(child) < limit
                        || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                    // stop here
                    recycleChildren(recycler, childCount - 1, i);
                    return;
                }
            }  
        }
    
    当滚动的时候,layoutState.mScrollingOffset 代表多少距离,不用创建新holder, 如果大于这个距离,就需要创建holder,主要用于预读取时候,比如有2列的holder,用mScrollingoffset排序,值越小代表马上滚出去了,需要立刻创建holder。这里主要用于缓存holder,代表已经产生了多少滚动量。 以上面代码为例,回收底部end的holder时,如果getDecoratedStart(child)< limit 就代表childcount -1 到 i位置(不包括i)的holder 都会被滚出了屏幕。 从底部回收holder.png

    如上图所示,item35的start 小于红线(limit) ,因此回收36和37位置holder。也可以采用item36、item37的start大于红线,每次回收一个,用小于红线,方便一次回收多个holder。

    scrollBy

    int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
            final int layoutDirection = delta > 0 ? LinearLayoutManager.LayoutState.LAYOUT_END : LinearLayoutManager.LayoutState.LAYOUT_START;
            final int absDelta = Math.abs(delta);
            updateLayoutState(layoutDirection, absDelta, true, state);
            final int consumed = mLayoutState.mScrollingOffset
                    + fill(recycler, mLayoutState, state, false);
            final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
            mOrientationHelper.offsetChildren(-scrolled);
            if (DEBUG) {
                Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);
            }
            mLayoutState.mLastScrollDelta = scrolled;
            return scrolled;
        }
    
        private void updateLayoutState(int layoutDirection, int requiredSpace,
                                       boolean canUseExistingSpace, RecyclerView.State state) {
            mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
            mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
            // calculate how much we can scroll without adding new children (independent of layout)
            scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
                    - mOrientationHelper.getEndAfterPadding();
        }
    

    1、scrollingOffset 用最后一个child的end 减去getEndAfterPadding 就是最后一个view被遮挡的部分,也是滚动不需要创建view的大小。
    2、mCurrentPosition 最后一个child的位置 + 加上layoutend的方向1。
    3、mOffset 最后一个child的end位置,就是下一个view填充的位置。
    4、comsumed等于不需要滚动的 + fill填充的空间 ,然后 调用offsetChildren(-scrolled)实现滚动。

    GridLayoutManager

    GridLayoutManager继承LinearLayoutManager,重写child的OnMeasure和onLayout方法。

    1、计算anchorPosition

    和LinearLayoutManager计算anchorinfo的三种方式一样,然后确保布局的anchorposition所在的spanIndex值为0,也就是保证从整行或者整列开始measure和layout。

     private void ensureAnchorIsInCorrectSpan(RecyclerView.Recycler recycler,
                RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection) {
            final boolean layingOutInPrimaryDirection =
                    itemDirection == LayoutState.ITEM_DIRECTION_TAIL;
            int span = getSpanIndex(recycler, state, anchorInfo.mPosition);
            if (layingOutInPrimaryDirection) {
                // choose span 0
                //只有span = 0 跳出循环,代表mPosition 是layout在新行的
                while (span > 0 && anchorInfo.mPosition > 0) {
                    anchorInfo.mPosition--;
                    span = getSpanIndex(recycler, state, anchorInfo.mPosition);
                }
            } else {
                // choose the max span we can get. hopefully last one
                final int indexLimit = state.getItemCount() - 1;
                int pos = anchorInfo.mPosition;
                int bestSpan = span;
                while (pos < indexLimit) {
                   //如果 postion+1  的index  小于 position 则表示positon +1 
                   //换行了,next的值是一定为0的。 position + 1就是layout
                   //开始的位置
                    int next = getSpanIndex(recycler, state, pos + 1);
                    if (next > bestSpan) {
                        pos += 1;
                        bestSpan = next;
                    } else {
                        break;
                    }
                }
                anchorInfo.mPosition = pos;
            }
        }
    

    getSpanIndex(recycler, state, anchorInfo.mPosition) 返回mPosition位置的index,index的值域为[0,spancount),如果为0,则mPostion应该把item放置的开始位置为新行的第0个位置。 如果为1,则mPostion的开始放置item位置就是当前行/列 1。
    如上所示, 当span = 0的时候,就返回该位置。span=0 就代表mPosition位置为行/列的第0个位置。

    2、计算每个item的大小

    计算的是非滑动方向,保证每个itemsize * spancount = totalSize。 计算每一个item的大小存到数组中,如果非滑动方向measuremode为match_parent 则totalSpacesize为parentSize。如果是wrap_content,则totalSize = maxChildSize/spansize(item占的格数) * spancount。

    static int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace) {
            if (cachedBorders == null || cachedBorders.length != spanCount + 1
                    || cachedBorders[cachedBorders.length - 1] != totalSpace) {
                cachedBorders = new int[spanCount + 1];
            }
            cachedBorders[0] = 0;
            int sizePerSpan = totalSpace / spanCount;
            int sizePerSpanRemainder = totalSpace % spanCount;
            int consumedPixels = 0;
            int additionalSize = 0;
            for (int i = 1; i <= spanCount; i++) {
                int itemSize = sizePerSpan;
                additionalSize += sizePerSpanRemainder;
                if (additionalSize > 0 && (spanCount - additionalSize) < sizePerSpanRemainder) {
                    itemSize += 1;
                    additionalSize -= spanCount;
                }
                consumedPixels += itemSize;
                cachedBorders[i] = consumedPixels;
            }
            return cachedBorders;
        }
    

    举例:totalSpace = 38 ,spanCount = 5 结果cachedBorders[1]=8 , cachedBorders[2]=8+7 = 15 ,cachedBorders[3]=15+8=23, cachedBorders[4]=23 + 7 = 30,cachedBorders[5]= 30 + 8 = 38,由于38 / 5 = 7...3 余数为3,因此,这个算法保证 将剩余的3均分给item。

    3、onMeasure和onLayout

     void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                         LinearLayoutManager.LayoutState layoutState, LinearLayoutManager.LayoutChunkResult result) {
            final int otherDirSpecMode = mOrientationHelper.getModeInOther();
            final boolean flexibleInOtherDir = otherDirSpecMode != View.MeasureSpec.EXACTLY;
            final int currentOtherDirSize = getChildCount() > 0 ? mCachedBorders[mSpanCount] : 0;
            // if grid layout's dimensions are not specified, let the new row change the measurements
            // This is not perfect since we not covering all rows but still solves an important case
            // where they may have a header row which should be laid out according to children.
            if (flexibleInOtherDir) {
                updateMeasurements(); //  reset measurements
            }
            final boolean layingOutInPrimaryDirection =
                    layoutState.mItemDirection == LinearLayoutManager.LayoutState.ITEM_DIRECTION_TAIL;
            int count = 0;
            int consumedSpanCount = 0;
            int remainingSpan = mSpanCount;
            if (!layingOutInPrimaryDirection) {
                int itemSpanIndex = getSpanIndex(recycler, state, layoutState.mCurrentPosition);
                int itemSpanSize = getSpanSize(recycler, state, layoutState.mCurrentPosition);
                remainingSpan = itemSpanIndex + itemSpanSize;
            }
            while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
                int pos = layoutState.mCurrentPosition;
                final int spanSize = getSpanSize(recycler, state, pos);
                if (spanSize > mSpanCount) {
                    throw new IllegalArgumentException("Item at position " + pos + " requires "
                            + spanSize + " spans but GridLayoutManager has only " + mSpanCount
                            + " spans.");
                }
                remainingSpan -= spanSize;
                if (remainingSpan < 0) {
                    break; // item did not fit into this row or column
                }
                View view = layoutState.next(recycler);
                if (view == null) {
                    break;
                }
                consumedSpanCount += spanSize;
                mSet[count] = view;
                count++;
            }
    
            if (count == 0) {
                result.mFinished = true;
                return;
            }
    
            int maxSize = 0;
            float maxSizeInOther = 0; // use a float to get size per span
    
            // we should assign spans before item decor offsets are calculated
            assignSpans(recycler, state, count, layingOutInPrimaryDirection);
            for (int i = 0; i < count; i++) {
                View view = mSet[i];
                if (layoutState.mScrapList == null) {
                    if (layingOutInPrimaryDirection) {
                        addView(view);
                    } else {
                        addView(view, 0);
                    }
                } else {
                    if (layingOutInPrimaryDirection) {
                        addDisappearingView(view);
                    } else {
                        addDisappearingView(view, 0);
                    }
                }
                calculateItemDecorationsForChild(view, mDecorInsets);
    
                measureChild(view, otherDirSpecMode, false);
                final int size = mOrientationHelper.getDecoratedMeasurement(view);
                if (size > maxSize) {
                    maxSize = size;
                }
                final GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams) view.getLayoutParams();
                final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view)
                        / lp.mSpanSize;
                if (otherSize > maxSizeInOther) {
                    maxSizeInOther = otherSize;
                }
            }
            if (flexibleInOtherDir) {
                // re-distribute columns
                guessMeasurement(maxSizeInOther, currentOtherDirSize);
                // now we should re-measure any item that was match parent.
                maxSize = 0;
                for (int i = 0; i < count; i++) {
                    View view = mSet[i];
                    measureChild(view, View.MeasureSpec.EXACTLY, true);
                    final int size = mOrientationHelper.getDecoratedMeasurement(view);
                    if (size > maxSize) {
                        maxSize = size;
                    }
                }
            }
    
            // Views that did not measure the maxSize has to be re-measured
            // We will stop doing this once we introduce Gravity in the GLM layout params
            for (int i = 0; i < count; i++) {
                final View view = mSet[i];
                if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
                    final GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams) view.getLayoutParams();
                    final Rect decorInsets = lp.mDecorInsets;
                    final int verticalInsets = decorInsets.top + decorInsets.bottom
                            + lp.topMargin + lp.bottomMargin;
                    final int horizontalInsets = decorInsets.left + decorInsets.right
                            + lp.leftMargin + lp.rightMargin;
                    final int totalSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize);
                    final int wSpec;
                    final int hSpec;
                    if (mOrientation == VERTICAL) {
                        wSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY,
                                horizontalInsets, lp.width, false);
                        hSpec = View.MeasureSpec.makeMeasureSpec(maxSize - verticalInsets,
                                View.MeasureSpec.EXACTLY);
                    } else {
                        wSpec = View.MeasureSpec.makeMeasureSpec(maxSize - horizontalInsets,
                                View.MeasureSpec.EXACTLY);
                        hSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY,
                                verticalInsets, lp.height, false);
                    }
                    measureChildWithDecorationsAndMargin(view, wSpec, hSpec, true);
                }
            }
    
            result.mConsumed = maxSize;
    
            int left = 0, right = 0, top = 0, bottom = 0;
            if (mOrientation == VERTICAL) {
                if (layoutState.mLayoutDirection == LinearLayoutManager.LayoutState.LAYOUT_START) {
                    bottom = layoutState.mOffset;
                    top = bottom - maxSize;
                } else {
                    top = layoutState.mOffset;
                    bottom = top + maxSize;
                }
            } else {
                if (layoutState.mLayoutDirection == LinearLayoutManager.LayoutState.LAYOUT_START) {
                    right = layoutState.mOffset;
                    left = right - maxSize;
                } else {
                    left = layoutState.mOffset;
                    right = left + maxSize;
                }
            }
            for (int i = 0; i < count; i++) {
                View view = mSet[i];
                GridLayoutManager.LayoutParams params = (GridLayoutManager.LayoutParams) view.getLayoutParams();
                if (mOrientation == VERTICAL) {
                    if (isLayoutRTL()) {
                        right = getPaddingLeft() + mCachedBorders[mSpanCount - params.mSpanIndex];
                        left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
                    } else {
                        left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
                        right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
                    }
                } else {
                    top = getPaddingTop() + mCachedBorders[params.mSpanIndex];
                    bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
                }
                // 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)
                            + ", span:" + params.mSpanIndex + ", spanSize:" + params.mSpanSize);
                }
                // Consume the available space if the view is not removed OR changed
                if (params.isItemRemoved() || params.isItemChanged()) {
                    result.mIgnoreConsumed = true;
                }
                result.mFocusable |= view.hasFocusable();
            }
            Arrays.fill(mSet, null);
        }
    

    总结一下分为以下几步
    1、 View view = layoutState.next(recycler); 从缓存策略中获取view,获取spancount个,存到View[]中。
    2、 assignSpans(recycler, state, count, layingOutInPrimaryDirection); 通过position把需要的参数绑定到view的layoutparams上,方便后面使用。 spanSize : 占的格数。 spanindex : 位于span中的第几个。
    3、 遍历数组,addView(view);
    4、 遍历数组,calculateItemDecorationsForChild(view, mDecorInsets); 获取decorInset。
    5、测量每一个view大小。measureChild(view, otherDirSpecMode, false)。非滚动的measureMode为otherDirMode,意思是采用viewgroup测量流程,也就是recyclerView和parent共同决定的mode。非滚动的大小为: getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize),也就是数组存的格子。
    6、如果非滚动mode为wrap_content,再次测量。
    measureChild(view, View.MeasureSpec.EXACTLY, true); 因为使用wrap_content测量的每一个item大小不一致。计算一个最大的itemMaxSize ,然后Math.min(itemMaxSize * spancount , oldDirSize) , 再次计算mCachedBorders数组。这次均分的数值就是最终的数值.。保证了当前行宽度一致,因此这次的mode为MeasureSpec.EXACTLY,再次记录高度的最大值。
    7、因为宽高有wrap,则高度可能不确定,所以宽度不一致,6解决了宽度为wrap的情况,可能导致高也改变了。这里再次测量。

     wSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY,
                                horizontalInsets, lp.width, false);
     hSpec = View.MeasureSpec.makeMeasureSpec(maxSize - verticalInsets,
                                View.MeasureSpec.EXACTLY);
    

    此时宽度,高度为定值。所以mode为View.MeasureSpec.EXACTLY
    8、layout 因为每次测量一列/行。 每行高度一致。宽度存在mCachedBorders中。

      left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
      right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
    

    这里的right还可以写成 right = getpaddingleft() + mCachedBorders[params.mSpanIndex+ params.spanSize]
    layoutDecoratedWithMargins(view, left, top, right, bottom) left 和top等都是包含了margin和decorinset的,因此child.layout需要减去。

    总结

    GridLayoutManager 和 LinearLayoutManager 承担着reyclerview的Measure和Layout的功能,两者的复用View逻辑相同,但是Grid的cacheScrap的最大个数为spancount(预读取) + 2 个, 而Linear的最大个数为 1(预读取) +2 个。 两者取anchor, 填充layoutState,scroller 逻辑相似。linear的measure是借助recyclerview测量,grid复写了measure ,grid每次会measure和layout spancount个view。

    相关文章

      网友评论

          本文标题:RecyclerView的layoutManager

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