在使用RecyclerView时,关于GridLayoutManager如何绘制布局的理解,我用的是android25.1.1中的API源码。
这里是一个大神关于RecyclerView绘制流程的详解 http://blog.csdn.net/hfyd_/article/details/53910631
背景:
以下讲解,是在RecyclerView设置了GridLayoutManager的情况下,方向为vertical
这里只讨论onlayoutchild(),从上边的文章中,我得知mLayout.onLayoutChildren(mRecycler, mState);这里的mlayout说是我们通过setLayoutManager()传入的,那我们就看一下具体是如何布局的。
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (state.isPreLayout()) {
cachePreLayoutSpanMapping();
}
super.onLayoutChildren(recycler, state);
if (DEBUG) {
validateChildOrder();
}
clearPreLayoutSpanMappingCache();
}
在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;
}
继续填充,而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++;
}
接下来是:
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添加到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或
下面我们来看一下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;
}
......
这样我们就得到了itemview的坐标,然后对其进行布局
......
layoutDecoratedWithMargins(view, left, top, right, bottom);
......
以上就是我对layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result)的理解,如果有什么不对
的地方还不吝指正。