归纳分发布局的三个步骤:
-
dispatchLayoutStep1(): 表示进行预布局,适配器更新、动画运行、保存当前视图的信息等工作;
-
dispatchLayoutStep2(): 表示对最终状态的视图进行实际布局,有必要时会多次执行;
-
dispatchLayoutStep3(): 表示布局最后一步,保存和触发有关动画的信息,相关清理等工作。
1.3 onDraw()
来到最后一步的绘制onDraw()
方法中,如果不需要一些特殊的效果,在TextView、ImageView控件中已经绘制完了。
@Override
public void onDraw(Canvas c) {
super.onDraw©;//所有itemView先绘制
//分别绘制ItemDecoration
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
1.4 绘制流程总结:
1、RecyclerView的itemView可能会被测量多次,如果RecyclerView的宽高是固定值或者match_parent,那么在onMeasure()
阶段是不会提前测量ItemView布局,如果RecyclerView的宽高是wrap_content,由于还没有知道RecyclerView的实际宽高,那么会提前在onMeasure()
阶段遍历测量itemView布局确定内容显示区域的宽高值来确定RecyclerView的实际宽高;
2、dispatchLayoutStep1()
、 dispatchLayoutStep2()
、 dispatchLayoutStep3()
这三个方法一定会执行,在RecyclerView的实际宽高不确定时,会提前多次执行dispatchLayoutStep1()
、 dispatchLayoutStep2()
方法,最后在onLayout()
阶段执行 dispatchLayoutStep3()
,如果有itemView发生改变会再次执行dispatchLayoutStep2()
;
3、正在的测量和布局itemView实际在dispatchLayoutStep2()
方法中。
RecyclerView的绘制三个步骤流程图:
2、LinearLayoutManager填充、测量、布局过程
RecyclerView的绘制经过measure、layout、draw三个步骤,但是itemView的真正布局时委托给各个的LayoutManager中处理,上面LinearLayoutManager可以知道dispatchLayoutStep2()
是实际布局视图步骤,通过LayoutManager调用onLayoutChildren()
方法进行布局itemView,它是绘制itemView的核心方法,表示从给定的适配器中列出所有相关的子视图。
2.1 onLayoutChildren()布局itemView
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// 1) 检查子类和其他变量找到描点坐标和描点位置
// 2) 从开始填补,从底部堆积
// 3) 从底部填补,从顶部堆积
// 4) 从底部堆积来满足需求
// 创建布局状态
if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);//移除所有子View
return;
}
}
ensureLayoutState();
mLayoutState.mRecycle = false;//禁止回收
//颠倒绘制布局
resolveShouldLayoutReverse();
final View focused = getFocusedChild();//获取目前持有焦点的child
if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();//重置锚点信息
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
//1. 计算更新描点位置和坐标
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
}
·······
//计算第一布局的方向
int startOffset;
int endOffset;
final int firstLayoutDirection;
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
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,如果没有则创建itemVie