detachAndScrapAttachedViews(recycler);//暂时分离已经附加的view,即将所有child detach并通过Scrap回收
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
mLayoutState.mNoRecycleSpace = 0;
//2.开始填充,从底部开始堆叠;
if (mAnchorInfo.mLayoutFromEnd) {
//描点位置从start位置开始填充ItemView布局
updateLayoutStateToFillStart(mAnchorInfo);
fill(recycler, mLayoutState, state, false);//填充所有itemView
//描点位置从end位置开始填充ItemView布局
updateLayoutStateToFillEnd(mAnchorInfo);
fill(recycler, mLayoutState, state, false);//填充所有itemView
endOffset = mLayoutState.mOffset;
}else { //3.向底填充,从上往下堆放;
//描点位置从end位置开始填充ItemView布局
updateLayoutStateToFillEnd(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
//描点位置从start位置开始填充ItemView布局
updateLayoutStateToFillStart(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
//4.计算滚动偏移量,如果有必要会在调用fill方法去填充新的ItemView
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
}
首先是状态判断和一些准备工作,对描点信息选择和更新, detachAndScrapAttachedViews(recycler)
暂时将已经附加的view分离,缓存Scrap中,下次重新填充时直接拿出来复用。然后计算是从哪个方向开始布局。布局算法如下:
-
1.通过检查子元素和其他变量,找到一个锚点坐标和一个锚点项的位置;
-
2.开始填充,从底部开始堆叠;
-
3.向底填充,从上往下堆放;
-
4.滚动以满足要求,如堆栈从底部。
2.2 fill()开始填充itemView
填充布局交给了fill()
方法,表示填充由layoutState定义的给定布局。为什么要fill两次呢?我们来看看fill()
方法:
//填充方法,返回的是填充itemView的像素,方便后续滚动时使用
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
recycleByLayoutState(recycler, layoutState);//回收滑出屏幕的view
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
//核心 == while()循环 ==
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {//一直循环,知道没有数据
layoutChunkResult.resetInternal();
//填充itemView的核心方法
layoutChunk(recycler, state, layoutState, layoutChunkResult);
······
if (layoutChunkResult.mFinished) {//布局结束,退出循环
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;//根据添加的child高度偏移计算
}
······
return start - layoutState.mAvailable;//返回这次填充的区域大小
}
fill()
核心就是一个while()循环,循环执行layoutChunk()
填充一个itemView到屏幕,同时返回这次填充的区域大小。首先根据屏幕还有多少剩余空间remainingSpace,根据这个数值减去子View所占的空间大小,小于0时布局子View结束,如果当前所有子View还没有超过remainingSpace时,调用layoutChunk()
安排View的位置。
2.3 layoutChunk()对itemView创建、填充、测量、布局
layoutChunk()
作为最终填充布局itemView的方法,对itemView创建、填充、测量、布局,主要有以下几个步骤:
-
1.
layoutState.next(recycler)
从缓存中获取itemView,如果没有则创建itemView; -
2.根据实际情况来添加itemView到RecyclerView中,最终调用的还是ViewGroup的
addView()
方法; -
3.
measureChildWithMargins()
测量itemView大小包括父视图的填充、项目装饰和子视图的边距; -
4.根据计算好的left, top, right, bottom通过
layoutDecoratedWithMargins()
使用坐标在RecyclerView中布局给定的itemView。
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//1.从缓存中获取或者创建itemView
View view = layoutState.next(recycler);//获取当前postion需要展示的View
······
//2.根据实际情况来添加itemView到RecyclerView中,最终调用的还是ViewGroup的addView()方法
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
}
//3.测量子View大小包括父视图的填充、项目装饰和子视图的边距
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
//计算一个ItemView的left, top, right, bottom坐标值
int left, top, right, bottom;
······
//4.使用坐标在RecyclerView中布局给定的itemView
//计算正确的布局位置,减去margin,计算所有视图的边界框(包括margin和装饰)
layoutDecoratedWithMargins(view, left, top, right, bottom);//调用child.layout进行布局
}
通过layoutState.next()
从缓存中获取itemView如果没有就创建一个新的itemView,然后addView()
根据实际情况来添加itemView到RecyclerView中,最终调用的还是ViewGroup的addView()
方法,接着通过 measureChildWithMargins()
测量子View大小包括父视图的填充、项目装饰和子视图的边距;最后getDecoratedMeasuredWidth()
通过计算好的left, top, right, bottom值在RecyclerView坐标中布局给定的itemView,注意这里的宽度是item+decoration的总宽度。
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
获取itemView,并且如果mScrapList
中有缓存的View 则使用缓存的view,如果没有mScrapList 就创建view,并添加到mScrapList 中。接下来getViewForPosition()
方法主要是RecyclerView的缓存机制,后续的文章会讲解到。
2.4 LinearLayoutManager填充、测量、布局过程总结:
onLayoutChildren()
表示从给定的适配器中列出所有相关的子视图,填充布局交给了fill()
方法,填充由layoutState定义的给定布局,while()循环执行layoutChunk()
填充一个itemView到屏幕,作为最终填充布局itemView的方法,layoutState.next(recycler)
从缓存中获取或者创建itemView,通过addView()添加itemView到RecyclerView中,其实最终调用的还是ViewGroup的addView()
方法,measureChildWithMargins()
测量itemView大小包括父视图的填充、项目装饰和子视图的边距,最后layoutDecoratedWithMargins()
根据计算好的left, top, right, bottom通过使用坐标在RecyclerView中布局给定的itemView。
流程图如下: