LinearLayoutManager并不是一個(gè)View,而是一個(gè)工具類,但是LinearLayoutManager承擔(dān)了一個(gè)View(當(dāng)然指的是RecyclerView)的布局、測(cè)量、子View 創(chuàng)建 復(fù)用 回收 緩存 滾動(dòng)等等操作。
一、回憶一下
上一篇文章Android Render(三)supportVersion 27.0.0源碼RecyclerView繪制流程解析已經(jīng)說(shuō)了 RecyclerView的繪制流程,dispatchLayoutStep1 dispatchLayoutStep2 dispatchLayoutStep3這三步都一定會(huì)執(zhí)行,只是在RecyclerView的寬高是寫死或者是match_parent的時(shí)候會(huì)提前執(zhí)行dispatchLayoutStep1 dispatchLayoutStep2者兩個(gè)方法。會(huì)在onLayout階段執(zhí)行dispatchLayoutStep3第三步。在RecyclerView 寫死寬高的時(shí)候onMeasure階段很容易,直接設(shè)定寬高。但是在onLayout階段會(huì)把dispatchLayoutStep1 dispatchLayoutStep2 dispatchLayoutStep3三步依次執(zhí)行。

二、onLayoutChildren開(kāi)始布局準(zhǔn)備工作
上圖是在RecyclerView中繪制三步驟對(duì)dispatchLayoutStep三個(gè)方法的調(diào)用??吹酱a我們可以知道是在dispatchLayoutStep2方法中調(diào)用LayoutManager的onLayoutChildren方法來(lái)布局ItemView的。
private void dispatchLayoutStep2() {
......略
// Step 2: Run layout
mState.mInPreLayout = false;
// 調(diào)用`LayoutManager`的`onLayoutChildren`方法來(lái)布局`ItemView`
mLayout.onLayoutChildren(mRecycler, mState);
......略
}
下圖是LinearLayoutManager對(duì)循環(huán)布局所有的ItemView的流程圖:

雖然在RecyclerView的源碼中會(huì)三步繪制處理,但是都不是真正做繪制布局測(cè)量的地方,真正的繪制布局測(cè)量都放在了不同的LayoutManager中了,我們就以LinearLayoutManager為例來(lái)分析一下。
在三中LayoutManager中,LinearLayoutManager應(yīng)該是最為簡(jiǎn)單的一種了吧。GridLayoutManager也是繼承LinearLayoutManager實(shí)現(xiàn)的,只是在layoutChunk方法中實(shí)現(xiàn)了不同的布局。
LinearLayoutManager布局從onLayoutChildren方法開(kāi)始:
//LinearLayoutManager布局從onLayoutChildren方法開(kāi)始
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm: 布局算法
// 1) by checking children and other variables, find an anchor coordinate and an anchor item position.
// 通過(guò)檢查孩子和其他變量,找到錨坐標(biāo)和錨點(diǎn)項(xiàng)目位置 mAnchor為布局錨點(diǎn) 理解為不具有的起點(diǎn).
// mAnchor包含了子控件在Y軸上起始繪制偏移量(coordinate),ItemView在Adapter中的索引位置(position)和布局方向(mLayoutFromEnd)
// 2) fill towards start, stacking from bottom 開(kāi)始填充, 從底部堆疊
// 3) fill towards end, stacking from top 結(jié)束填充,從頂部堆疊
// 4) scroll to fulfill requirements like stack from bottom. 滾動(dòng)以滿足堆棧從底部的要求
......略
ensureLayoutState();
mLayoutState.mRecycle = false;
// resolve layout direction 設(shè)置布局方向(VERTICAL/HORIZONTAL)
resolveShouldLayoutReverse();
//重置繪制錨點(diǎn)信息
mAnchorInfo.reset();
// mStackFromEnd需要我們開(kāi)發(fā)者主動(dòng)調(diào)用,不然一直未false
// VERTICAL方向?yàn)閙LayoutFromEnd為false HORIZONTAL方向是為true
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
// ====== 布局算法第 1 步 ======: 計(jì)算更新保存繪制錨點(diǎn)信息
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
......略
// HORIZONTAL方向時(shí)開(kāi)始繪制
if (mAnchorInfo.mLayoutFromEnd) {
// ====== 布局算法第 2 步 ======: fill towards start 錨點(diǎn)位置朝start方向填充ItemView
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
// 填充第一次
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// ====== 布局算法第 3 步 ======: fill towards end 錨點(diǎn)位置朝end方向填充ItemView
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
// 填充第二次
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
......略
} else {
// VERTICAL方向開(kāi)始繪制
// ====== 布局算法第 2 步 ======: fill towards end 錨點(diǎn)位置朝end方向填充ItemView
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
// 填充第一次
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
final int lastElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// ====== 布局算法第 3 步 ======: fill towards start 錨點(diǎn)位置朝start方向填充ItemView
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
// 填充第二次
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
......略
}
// ===布局算法第 4 步===: 計(jì)算滾動(dòng)偏移量,如果有必要會(huì)在調(diào)用fill方法去填充新的ItemView
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
}
layout algorithm: 布局算法:
- 1.通過(guò)檢查孩子和其他變量,找到錨坐標(biāo)和錨點(diǎn)項(xiàng)目位置 mAnchor為布局錨點(diǎn) 理解為不具有的起點(diǎn),mAnchor包含了子控件在Y軸上起始繪制偏移量(coordinate),ItemView在Adapter中的索引位置(position)和布局方向(mLayoutFromEnd)。
- 2.開(kāi)始填充, 從底部堆疊
- 3.結(jié)束填充,從頂部堆疊
- 4.滾動(dòng)以滿足堆棧從底部的要求
這四步驟我都在代碼中標(biāo)記出來(lái)了。
至于為什么有好幾次會(huì)調(diào)用到fill方法,什么formEnd,formStart,這個(gè)請(qǐng)看圖:

示意圖圖來(lái)源:http://blog.csdn.net/qq_23012315/article/details/50807224
圓形紅點(diǎn)就是我們布局算法在第一步updateAnchorInfoForLayout方法中計(jì)算出來(lái)的填充錨點(diǎn)位置。
第一種情況是屏幕顯示的位置在RecyclerView的最底部,那么就只有一種填充方向?yàn)?code>formEnd
第二種情況是屏幕顯示的位置在RecyclerView的頂部,那么也只有一種填充方向?yàn)?code>formStart
第三種情況應(yīng)該是最常見(jiàn)的,屏幕顯示的位置在RecyclerView的中間,那么填充方向就有formEnd和formStart兩種情況,這就是 fill 方法調(diào)用兩次的原因。
上面是RecyclerView的方向?yàn)?code>VERTICAL的情況,當(dāng)為HORIZONTAL方向的時(shí)候填充算法是不變的。
二、fill 開(kāi)始布局ItemView
fill核心就是一個(gè)while循環(huán),while循環(huán)執(zhí)行了一個(gè)很核心的方法就是:
layoutChunk ,此方法執(zhí)行一次就填充一個(gè)ItemView到屏幕。
看一下 fill 方法的代碼:
// fill填充方法, 返回的是填充ItemView需要的像素,以便拿去做滾動(dòng)
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// 填充起始位置
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
//如果有滾動(dòng)就執(zhí)行一次回收
recycleByLayoutState(recycler, layoutState);
}
// 計(jì)算剩余可用的填充空間
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
// 用于記錄每一次while循環(huán)的填充結(jié)果
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
// ================== 核心while循環(huán) ====================
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
// ====== 填充itemView核心填充方法 ====== 屏幕還有剩余可用空間并且還有數(shù)據(jù)就繼續(xù)執(zhí)行
layoutChunk(recycler, state, layoutState, layoutChunkResult);
}
......略
// 填充完成后修改起始位置
return start - layoutState.mAvailable;
}
代碼看起來(lái)還是很簡(jiǎn)潔明了的。解釋都加了注釋,就不再羅列出來(lái)了??吹竭@里我們就知道了 fill 下一步的核心方法就是 layoutChunk , 此方法執(zhí)行一次就是填充一個(gè)ItemView。
三、layoutChunk 創(chuàng)建 填充 測(cè)量 布局 ItemView
layoutChunk 方法主要功能標(biāo)題已經(jīng)說(shuō)了 創(chuàng)建、填充 、測(cè)量 、布局 一個(gè)ItemView,一共有四步:
- 1
layoutState.next(recycler)方法從一二級(jí)緩存中獲取或者是創(chuàng)建一個(gè)ItemView。 - 2
addView方法加入一個(gè)ItemView到ViewGroup中。 - 3
measureChildWithMargins方法測(cè)量一個(gè)ItemView。 - 4
layoutDecoratedWithMargins方法布局一個(gè)ItemView。布局之前會(huì)計(jì)算好一個(gè)ItemView的left, top, right, bottom位置。
其實(shí)就是這四個(gè)關(guān)鍵步驟:
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// ====== 第 1 步 ====== 從一二級(jí)緩存中獲取或者是創(chuàng)建一個(gè)ItemView
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
// if we are laying out views in scrap, this may return null which means there is
// no more items to layout.
result.mFinished = true;
return;
}
// ====== 第 2 步 ====== 根據(jù)情況來(lái)添加ItemV,最終調(diào)用的還是ViewGroup的addView方法
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
// ====== 第 3 步 ====== 測(cè)量一個(gè)ItemView的大小包含其margin值
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
// 計(jì)算一個(gè)ItemView的left, top, right, bottom坐標(biāo)值
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
// 根據(jù)得到的一個(gè)ItemView的left, top, right, bottom坐標(biāo)值來(lái)確定其位置
// ====== 第 4 步 ====== 確定一個(gè)ItemView的位置
layoutDecoratedWithMargins(view, left, top, right, bottom);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+ (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
}
// Consume the available space if the view is not removed OR changed
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
四、LinearLayoutManager填充、測(cè)量、布局過(guò)程總結(jié)
從 RecyclerView 繪制觸發(fā)的一開(kāi)始,就會(huì)把需要繪制的ItemView做一次while循環(huán)繪制一次,中間要經(jīng)歷好多個(gè)步驟,還設(shè)計(jì)到緩存。RecyclerView的繪制處理等還是比較復(fù)雜的。