深入理解 RecyclerView 的绘制流程和滑动原理,android开发平台的框架原理

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。

流程图如下:

深入理解 RecyclerView 的绘制流程和滑动原理,android开发平台的框架原理_第1张图片

你可能感兴趣的:(程序员,架构,移动开发,android)