Android随笔-GridLayoutManager

在使用RecyclerView时,关于GridLayoutManager如何绘制布局的理解,我用的是android25.1.1中的API源码。

这里是一个大神关于RecyclerView绘制流程的详解      http://blog.csdn.net/hfyd_/article/details/53910631

背景:

   以下讲解,是在RecyclerView设置了GridLayoutManager的情况下,方向为vertical

流程onMeasure——onLayout——ondraw(fill)


这里只讨论onlayoutchild(),从上边的文章中,我得知mLayout.onLayoutChildren(mRecycler, mState);这里的mlayout说是我们通过setLayoutManager()传入的,那我们就看一下具体是如何布局的。
GridLayoutManager中实现了这个方法:

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (state.isPreLayout()) {
            cachePreLayoutSpanMapping();
        }
        super.onLayoutChildren(recycler, state);
        if (DEBUG) {
            validateChildOrder();
        }
        clearPreLayoutSpanMappingCache();
    }

我们看到调用了super.onlayotChildren();所以继续进入查看(继承了LinearLayoutManager
在LinearLayoutManager中,这个方法调用了fill();所以我们继续到fill()中查看:

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

在while中调用了layoutChunk(),对子view进行测量,布局,绘制。所以这就是我要重点说的部分。在while中判断remainingSpace 是否大于0,我认为这是判断子view还需不需要
继续填充,而layoutChunk()是对RecyclerView中的一行进行绘制(假设我们设置的方向是vertical).现在我们去layoutChunk()去看一下
在GridLayoutManager类中:

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

这一块代码,为每一行(即chunk)分配itemview.
接下来是:

assignSpans(recycler, state, count, consumedSpanCount, 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 LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view) /
                    lp.mSpanSize;
            if (otherSize > maxSizeInOther) {
                maxSizeInOther = otherSize;
            }


assignSpans()

这个方法为每一个itemview设置跨度的大小(即此itemview可以占列数)和引索(即此itemview在这一行中是第几个,例,设置列数为3,第一个是0,第二个是1);此方法

完成之后,将每一个itemview添加到RecyclerView中。

calculateItemDecorationsForChild(view, mDecorInsets);
这个方法是计算itemview的offset,最终会调用getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state),这个方法是在我们继承
RecyclerView.ItemDecoration类,需要自己实现的,即设置itemview之间的间隔
下面我们来说一下measureChild方法的作用:

 private void measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured) {
        final LayoutParams lp = (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 availableSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize);
        final int wSpec;
        final int hSpec;
        if (mOrientation == VERTICAL) {
            wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
                    horizontalInsets, lp.width, false);
            hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(),
                    verticalInsets, lp.height, true);
        } else {
            hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
                    verticalInsets, lp.height, false);
            wSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getWidthMode(),
                    horizontalInsets, lp.width, true);
        }
        measureChildWithDecorationsAndMargin(view, wSpec, hSpec, alreadyMeasured);
    }

 private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec,
            boolean alreadyMeasured) {
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
        final boolean measure;
        if (alreadyMeasured) {
            measure = shouldReMeasureChild(child, widthSpec, heightSpec, lp);
        } else {
            measure = shouldMeasureChild(child, widthSpec, heightSpec, lp);
        }
        if (measure) {
            child.measure(widthSpec, heightSpec);
        }
    }


measureChild()是用一些已知的信息测量itemview宽度和高度,availableSpaceInOther 是这个itemview占用列的总长度,在getChildMeasureSpec方法中,计算出了itemview
的width和height,它的返回结果包括了它的长度和模式,return MeasureSpec.makeMeasureSpec(resultSize, resultMode);最后调用measureChildWithDecorationsAndMargin
对itemview的width和height进行设置。
在测量完itemview后,即得到itemviewwidth与height,然后对其进行布局,即设置itemview的left,top,right,bottom


int left = 0, right = 0, top = 0, bottom = 0;
        if (mOrientation == VERTICAL) {
                //从锚点开始计算top,bottom
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = bottom - maxSize;//maxSize是指itemview所在行中高度最大的itemview的高度
            } 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;
            }
        }
        for (int i = 0; i < count; i++) {
            View view = mSet[i];
            LayoutParams params = (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.


计算完top与bottom后,会计算right和left,上述代码在计算时用到了mCachedBorders[mSpanCount - params.mSpanIndex],这个是view的长度,mSpanCount即列数,
params.mSpanIndex是这个itemview在这一行的索引,这个引索是在上面调用assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count,
int consumedSpanCount, boolean layingOutInPrimaryDirection)时,得到的:

......
private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count,
            int consumedSpanCount, boolean layingOutInPrimaryDirection) {
        // spans are always assigned from 0 to N no matter if it is RTL or not.
        // RTL is used only when positioning the view.
        int span, start, end, diff;
        // make sure we traverse from min position to max position
        if (layingOutInPrimaryDirection) {
            start = 0;
            end = count;
            diff = 1;
        } else {
            start = count - 1;
            end = -1;
            diff = -1;
        }
        span = 0;
        for (int i = start; i != end; i += diff) {
            View view = mSet[i];
            LayoutParams params = (LayoutParams) view.getLayoutParams();
            params.mSpanSize = getSpanSize(recycler, state, getPosition(view));
            params.mSpanIndex = span;
            span += params.mSpanSize;
        }
    }
.......


final boolean layingOutInPrimaryDirection =layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;
 mLayoutState.mItemDirection =mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
这里有一个layingOutInPrimaryDirection,它是由mShouldReverseLayout决定的,mShouldReverseLayout的意思是:确定LayoutManager如何排列view,( from end to start或

from start to end).通过这个layingOutInPrimaryDirection我们就可以确定如何如何赋值params.mSpanIndex
下面我们来看一下mCachedBorders[]数组是如何得到的:

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

由此可知:mCachedBorders[]是一个长度为列数+1的数组,它里边的值代表的是itemview的长度,第i个大约为i*itemsize

这样我们就得到了itemview的坐标,然后对其进行布局

......
layoutDecoratedWithMargins(view, left, top, right, bottom);
......

之后就可以进行绘制。


以上就是我对layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result)的理解,如果有什么不对
的地方还不吝指正。



你可能感兴趣的:(Android随笔)