在使用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中。
这个方法是计算itemview的offset,最终会调用getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state),这个方法是在我们继承calculateItemDecorationsForChild(view, mDecorInsets);
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)的理解,如果有什么不对
的地方还不吝指正。