Recyclerview的LayoutManager是怎么布局的

  • 首先分析一下Recyclerview最核心的部分,也就是计算子view,layout 子view,计算剩余空间,直到剩余空间为0的部分,就是dispatchLayoutStep2
private void dispatchLayoutStep2() {
    ......
    mLayout.onLayoutChildren(mRecycler, mState);
    ......
}

可以看到这个部分其实是委托给了layoutmanager来完成的。

  • 我们首先来看下LinearLayoutManager的onLayoutChildren.
//LinearLayoutManager 的onLayoutChildren方法
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    ......
    updateAnchorInfoForLayoutExpose(state, mAnchorInfo);
    ......
    detachAndScrapAttachedViews(recycler);
    if (mAnchorInfo.mLayoutFromEnd) {
        // fill towards start 和下面的逻辑分支是反过来的,仅看下面的逻辑为讲解。
        ......
    } else {
        // fill towards end
        updateLayoutStateToFillEndExpose(mAnchorInfo);
        mLayoutState.mExtra = extraForEnd;
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;
        if (mLayoutState.mAvailable > 0) {
            extraForStart += mLayoutState.mAvailable;
        }
        // fill towards start
        updateLayoutStateToFillStartExpose(mAnchorInfo);
        mLayoutState.mExtra = extraForStart;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;
    }
    ......
}
  • 首先updateAnchorInfoForLayout是用来更新锚点信息的,锚点是用以layoutmanager渲染view的坐标
  • 第一步找锚点和位置确定相对坐标和 item 位置;
  • 对容器上的 View 做detach 和回收处理见调用detachAndScrapAttachedViews
  • 填充 View 到可用空间,以锚点为界,在布局方向以相反的两个方向扩张来填充可用空间

以描点为界向两个方向填充的算法示意图如下,其中红色点为描点位置:


Recyclerview的LayoutManager是怎么布局的_第1张图片
  • 接着我们来看fill方法,这个方法就是用view来填充剩余空间
protected int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
                       RecyclerView.State state, boolean stopOnFocusable) {
    ......
    if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
        ......
        recycleByLayoutState(recycler, layoutState);
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    while (remainingSpace > 0 && layoutState.hasMore(state)) {
        layoutChunkResultCache.resetInternal();
        layoutChunk(recycler, state, layoutState, layoutChunkResultCache);
        if (layoutChunkResultCache.mFinished) {
            break;
        }
        layoutState.mOffset += layoutChunkResultCache.mConsumed * layoutState.mLayoutDirection;
        if (!layoutChunkResultCache.mIgnoreConsumed || mLayoutState.mScrapList != null
           || !state.isPreLayout()) {
            layoutState.mAvailable -= layoutChunkResultCache.mConsumed;
            // we keep a separate remaining space because mAvailable is important for recycling
            remainingSpace -= layoutChunkResultCache.mConsumed;
        }         ......
    }
    if (DEBUG) {
        validateChildOrderExpose();
    }
    return start - layoutState.mAvailable;
}
  • 提醒一下LayoutState.LAYOUT_START和LAYOUT_END意思是Toward Start还是Toward End,意思是LAYOUT_END是从上往下布局。
  • 这个方法本质就是不断的把子view放到空余空间中,直到remainingSpace<=0,也就是空间不足了。
  • 首先会通过recycleByLayoutState方法移除已经滑出屏幕以外的子视图
=======
for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedEnd(child) > limit
                        || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                    // stop here
                    recycleChildren(recycler, 0, i);
                    return;
                }
            }
======
  • 可以看到是计算出第一个下边界大于limit的view,然后把这个view之前的view全部回收掉。
  • 然后通过一个循环的朝着一个布局方向每次调用layoutChunk添加 View,并适时调节剩余空间和下次 layout 的 offset,直到剩下的空间不足循环结束。而子视图逐个的测量和布局就是在layoutChunk这个方法里进行的,看下面部分源码:
  • 而真正对子view进行measure和layout的方法是layoutChunk。
protected void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                               LayoutState layoutState, LayoutChunkResult result) {
    View view = layoutState.next(recycler);
    ......
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
    if (layoutState.mScrapList == null) {
        // can not find in scrapList
        if (mShouldReverseLayoutExpose == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addView(view);
        } else {
            addView(view, 0);
        }
    }
    ......
    measureChildWithMargins(view, 0, 0);
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
    int left, top, right, bottom;
    if (getOrientation() == 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 {
    ......
    }
    // We calculate everything with View's bounding box (which includes decor and margins)
    // To calculate correct layout position, we subtract margins.
    layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
            right - params.rightMargin, bottom - params.bottomMargin);
    ......
    result.mFocusable = view.isFocusable();
}
  • 这里面做了这几件事

    • 1:measureChild measure子view的宽高
    • 更新mConsumed,要放置这个view一定会消耗一部分空间,所以更新mConsumed的值
    • layout 子view,根据mOffset和measure出来的子view的宽高,把子view放到该放的地方
  • 然后就这么继续下去,知道remainSpace小于0,无法放置新的view了。

  • GridLayoutManager继承自LinearLayoutManager,核心的布局方法差不多

@Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (state.isPreLayout()) {
            cachePreLayoutSpanMapping();
        }
        super.onLayoutChildren(recycler, state);
        if (DEBUG) {
            validateChildOrder();
        }
        clearPreLayoutSpanMappingCache();
    }
  • 可以看到调用的是LinearLayoutManager的onLayoutChildren。
  • 不一样的地方在于layoutChunk这个方法,LinearLayoutManager一行只放一个,GridLayoutManager一行会放多个,具体几个取决于他的列数。
   @Override
   
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
     ················
        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++;
        }

     ································
        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, consumedSpanCount, layingOutInPrimaryDirection);
        for (int i = 0; i < count; i++) {
·········································
            addView()
            calculateItemDecorationsForChild(view, mDecorInsets);

            measureChild(view, otherDirSpecMode, false);
            final int size = mOrientationHelper.getDecoratedMeasurement(view);
            if (size > maxSize) {
                maxSize = size;
            }
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view) /
                    lp.mSpanSize;
            if (otherSize > maxSizeInOther) {
                maxSizeInOther = otherSize;
            }
        }
      

     ······························
        result.mConsumed = maxSize;

        int left = 0, right = 0, top = 0, bottom = 0;
        if (mOrientation == VERTICAL) {
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = bottom - maxSize;
            } else {
                top = layoutState.mOffset;
                bottom = top + maxSize;
            }
        } else {
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = right - maxSize;
            } else {
                left = layoutState.mOffset;
                right = left + maxSize;
            }
        }
 ·······························
  • 其实核心逻辑就是取一行中最高的maxSize作为mConsumed已经消耗的空间,如果view不一样高,其他矮的view会被重新计算,拉到和最高的view 一样高,所以gridlayoutmanager是一定一样高

你可能感兴趣的:(Recyclerview的LayoutManager是怎么布局的)