RecyclerView應(yīng)該是我們使用非常頻繁的一個(gè)組件 我們也有必要學(xué)習(xí)分析一下RecyclerView#onLayout工作流程 對(duì)我們?nèi)蘸蠓治鰞?yōu)化RecyclerView也會(huì)有幫助
美其名曰 知其然也知所以然
預(yù)布局是什么?
這個(gè)問(wèn)題其實(shí)我們可以先閱讀完下面的代碼 然后再來(lái)回想這個(gè)問(wèn)題 所以我先給出結(jié)論
首先預(yù)布局就是先布局一次(??湊個(gè)字?jǐn)?shù)) 然后會(huì)形成一個(gè)快照(pre-layout) 如item1234 然后再布局一次(post-layout) 形成另外一張快照 item134 這樣我們其實(shí)就知道了整個(gè)動(dòng)畫軌跡 就可以生成動(dòng)畫
上面看不懂 沒(méi)關(guān)系 看下圖??

下面分別是三種狀態(tài)
- 初始狀態(tài)
- pre-layout(預(yù)布局階段 生成一個(gè)快照 詳細(xì)代碼我們會(huì)在下面分析)
- post-layout(布局階段 生成另一個(gè)快照)
然后我們就知道了item3的初始位置和終止位置 就可以生成動(dòng)畫并執(zhí)行
源碼調(diào)試手段
因?yàn)槲覀兎治鲈创a的過(guò)程中 經(jīng)常有很多個(gè)分支 不知道如何是好?? 所以就需要斷點(diǎn)調(diào)試手段了
首先我們啟動(dòng)一個(gè)AVD模擬器 然后AVD的版本號(hào)一定要和compileSdkVersion版本號(hào)是一樣的 不然會(huì)出現(xiàn)代碼行數(shù)不一致導(dǎo)致調(diào)試不了問(wèn)題
然后我們就可以愉快的找一個(gè)調(diào)試點(diǎn)開(kāi)始調(diào)試?yán)?/p>
RecyclerView#onLayout流程
先從onLayout開(kāi)始閱讀代碼(主要是我找不到閱讀入口 就先隨便找一個(gè)??)
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
調(diào)用了dispatchLayout()方法 繼續(xù)看一下
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
我們發(fā)現(xiàn)會(huì)按照調(diào)用順序
dispatchLayoutStep1()-> dispatchLayoutStep2()-> dispatchLayoutStep3()
我們挨個(gè)分析一下各方法功能 然后最后會(huì)做一個(gè)總結(jié) 朋友們也可以先點(diǎn)擊??先看一眼結(jié)論
開(kāi)始分析吧 任重而道遠(yuǎn)
private void dispatchLayoutStep1() {
......
//設(shè)置mInPreLayout 預(yù)布局標(biāo)志位
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mLayoutStep = State.STEP_LAYOUT;
......
//調(diào)用LayoutManager.onLayoutChildren方法
mLayout.onLayoutChildren(mRecycler, mState);
......
}
我們看到這里設(shè)置了mInPreLayout 我們草率的先認(rèn)定它是預(yù)布局的標(biāo)志位 當(dāng)然結(jié)果也是對(duì)的??
全局搜索一下mInPreLayout的賦值 發(fā)現(xiàn)只有這里兩處對(duì)mInPreLayout賦值
一處是onMeasure()時(shí)
if (mState.mRunPredictiveAnimations) {
mState.mInPreLayout = true;
}
那么mState.mRunPredictiveAnimations一定是true 不然就沒(méi)辦法判斷了

我們斷點(diǎn)驗(yàn)證一下 發(fā)現(xiàn)
mState.mRunPredictiveAnimations確實(shí)是true
LinearLayoutManager#onLayoutChildren()
我們這里以LinearLayoutManager來(lái)分析 其他幾個(gè)LayoutManger其實(shí)也差不太多
根據(jù)注釋 我們看到onLayoutChildren主要做了四件事情
- 檢查children 找到第一個(gè)需要變化(刪除或者添加)的position
- 從底向上填充布局
- 從上向下填充布局
- 滾動(dòng)布局來(lá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.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
.......
//將所有view先回收
detachAndScrapAttachedViews(recycler);
final int firstLayoutDirection;
//從下往上
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForStart;
//填充布局
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtraFillSpace = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
//從上往下 和上面一樣的流程
......
}
......
}
下面就到了我們熟悉的fill()方法 我們可能在很多文章都看到過(guò)這個(gè)方法 會(huì)依次調(diào)用layoutChunk來(lái)對(duì)布局進(jìn)行填充 這邊我們需要重點(diǎn)關(guān)注一下預(yù)布局相關(guān)的不同點(diǎn)?? 畢竟我們這篇文章的主題是分析預(yù)布局的啊
我們看一下fill()方法 這邊我們會(huì)重點(diǎn)關(guān)注一下remainSpace 我們之前一直在提的 預(yù)布局會(huì)形成一張快照 postLayout也會(huì)形成另一張快照 fill()方法中會(huì)對(duì)兩次布局做不同的處理
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;
......
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;//可用剩余布局空間
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
//只要remainingSpace>0 并且position<itemsCount
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
//layout item
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
//這里我們重點(diǎn)看一下 只有Post-Layut 或者mIgnoreConsumed為false remainingSpace才會(huì)減少 ①
//否則remainingSpace不會(huì)減少
if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResult.mConsumed;
}
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
return start - layoutState.mAvailable;
}
我們將上面的代碼精簡(jiǎn)了一下 并且寫了一些注釋 我們發(fā)現(xiàn)
他是一個(gè)while循環(huán) 直到remainingSpace < 0 或者 position > itemsCount 才會(huì)布局
我們上面提了好幾次的 Pre-Layout 會(huì)形成一張1234的快照 這張快照包含即將要?jiǎng)h除的item2 以及刪除item2之后填充的item4
這里就會(huì)產(chǎn)生一個(gè)疑問(wèn) 他是如何計(jì)算Pre-Layout的高度呢?
我們看到最開(kāi)始代碼int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
??哈哈哈哈哈哈哈哈 結(jié)果出來(lái)了 就是layoutState.mExtraFillSpace 然而現(xiàn)實(shí)給我好好的上了一課?? 調(diào)試發(fā)現(xiàn)上面的變量為0
答案:其實(shí)是上面注釋①的地方 只有非Pre-Layout或者mIgnoreConsumed為false remainingSpace才會(huì)減少
我搜了一下mIgnoreConsumed什么時(shí)候會(huì)為false
// Consume the available space if the view is not removed OR changed
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
只有當(dāng)需要remvoe 或者需要需要change 才會(huì)ignore
所以 朋友們 ??結(jié)果很明顯了啊 預(yù)布局的時(shí)候 remainingSpace會(huì)忽略需要remove的item 所以會(huì)形成一張1234的快照
dispatchLayoutStep1()終于分析完了 分析源碼的過(guò)程總是枯燥又帶著一點(diǎn)收獲啊
dispatchLayoutStep1()小總結(jié)
dispatchLayoutStep1()也就是我們今天需要分析的Pre-Layout的流程 生成了一張即將消失的內(nèi)容+即將顯示內(nèi)容的快照
然后我們接著分析一下dispatchLayoustStep2() 也就是Post-Layout
分析一下 Post-Layout??
我們先看一眼dispatchLayoutStep2的源碼
private void dispatchLayoutStep2() {
// Step 2: Run layout
//將mInPreLayout置為false
//這里表示預(yù)布局階段已經(jīng)正式結(jié)束了
mState.mInPreLayout = false;
//又到了熟悉的onLayoutChildren()環(huán)節(jié)
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
}
上面的源碼比較簡(jiǎn)短 我們發(fā)現(xiàn)dispatchLayoutStep2()方法中 首先mState.mInPreLayout = false; 表示Pre-Layout已經(jīng)結(jié)束了 然后進(jìn)入了我們熟悉的onLayoutChildren環(huán)節(jié)
我們上面剛剛分析過(guò)onLayoutChildren 因?yàn)?strong>state.isPreLayout()一定為false 所以remainingSpace一定會(huì)扣除所有空間
所以 結(jié)論出現(xiàn)了! dispatchLayoutStep2會(huì)生成一張item134的快照
等等! 我寫到這里的時(shí)候 突然引發(fā)了我一個(gè)另一個(gè)疑惑 上面所說(shuō)的并不能證明dispatchLayoutStep2會(huì)生成一張item134的快照?只能說(shuō)明會(huì)生成一張item1234?
咋回事啊? 然后我又去徹底翻了一遍源碼 終于找到了根源所在 :-(
我們之前沒(méi)有分析layoutChunk方法 這個(gè)方法在fill()中 會(huì)從recycler中獲取合適的ViewHolder 和View 然后添加到RecyclerView中
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//根據(jù)當(dāng)前position 獲取View
View view = layoutState.next(recycler);
......
}
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
//根據(jù)position獲取View
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
這里會(huì)根據(jù)mCurrentPosition獲取對(duì)應(yīng)的ViewHolder 然后將View添加到RecyclerView 但是我們還是沒(méi)辦法得到 為什么Post-Layout會(huì)生成item134呢?
Post-Layout和Pre-Layout 還有兩個(gè)地方有區(qū)別
public int getItemCount() {
return mInPreLayout
? (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout)
: mItemCount;
}
我們發(fā)現(xiàn)預(yù)布局和后布局的ItemCount是不一樣的
另外就是我們?cè)?code>fill()之前 會(huì)先回收所有的ViewHolder 想看scrapView代碼可以直接點(diǎn)擊跳轉(zhuǎn)看一下
scrapView 會(huì)將所有的ViewHolder放入mAttachedScrap中
而mAttachedScrap中存在position分別為0、0、1、2的四個(gè)表項(xiàng) 如下圖(被Remove的ViewHolder 的 position會(huì)置為0)

根據(jù)上面兩個(gè)條件 我們會(huì)發(fā)現(xiàn) 在fill()的過(guò)程中 只會(huì)生成item134快照
終于分析出來(lái)為啥是item134了 凌晨一點(diǎn)還在碼字 腦子轉(zhuǎn)的太累了??
如果你還想問(wèn) 那什么時(shí)候把刪除的position置為0的? 我只能猜測(cè)是(不能保證)processAdapterUpdatesAndSetAnimationFlags(); 然后就只能靠你自己分析了 我已經(jīng)分析不動(dòng)了??
<span id = "dispatch方法">小總結(jié)</span>
1. onLayoutChildren()
- dispatchLayoutStep1()
預(yù)布局階段 生成布局快照(remainingSpace忽略即將remove的item) - dispatchLayoutStep2()
post-layout階段 生成布局快照(不帶即將remove的item) - dispatchLayoutStep3()
執(zhí)行動(dòng)畫 因?yàn)樯厦鎯蓚€(gè)步驟已經(jīng)生成了開(kāi)始狀態(tài)和結(jié)束狀態(tài)的快照 所以我們可以拿到動(dòng)畫的起始位置和終止位置
思考另外一個(gè)問(wèn)題,RecyclerView刪除動(dòng)畫是如何執(zhí)行的?
我們上面已經(jīng)分析了dispatchLayoutStep1()和dispatchLayoutStep2 已經(jīng)得到了兩次快照所有item的位置信息 接下來(lái)就可以開(kāi)始執(zhí)行動(dòng)畫了 執(zhí)行動(dòng)畫的方法是dispatchLayoutStep3() 代碼流程等我睡醒再分析一下吧?? 太困了
<span id = "scrap"> detachAndScrapAttachedViews ()</span>
我們看到onLayoutChildren的時(shí)候 會(huì)先調(diào)用detachAndScrapAttachedViews 將所有View detach 然后再fill() 我們分析一下detachAndScrapAttachedViews方法做了哪些事情
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
//如果isInvalid() 就調(diào)用removeViewAt
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
//detachView
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
這里又不知道會(huì)走哪個(gè)分支了?? 不過(guò) Debug是我們的好伙伴啊?? 我們斷點(diǎn)發(fā)現(xiàn) 是走下面分支的 我們繼續(xù)往下分析??(分析源碼就是這樣 點(diǎn)到為止)
上面的detachViewAt(index); 主要是將view從children中雙向刪除 會(huì)調(diào)用到ViewGroup中removeFromArray 不是我們這里的重點(diǎn)吶 我們先跳過(guò) 我們分析一下recycler.scrapView(view);
然后會(huì)將所有的View回收到mAttachedScrap中
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
//如果holder沒(méi)有失效 沒(méi)有被移除 則放入mAttachedScrap中
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
//只有當(dāng)holder需要改變才會(huì)放入mChangedScrap中
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
總結(jié)
能堅(jiān)持看完的都是勇士 分析一篇源碼也真的是很累啊 不過(guò)還是受益良多 分析完預(yù)布局源碼 現(xiàn)在對(duì)RecyclerView的布局流程認(rèn)知更加清晰了 對(duì)以后優(yōu)化和分析ReyclerView也會(huì)很有幫助 如果有對(duì)RecyclerView復(fù)用機(jī)制感興趣的朋友 可以閱讀我另外一篇文章啊 鏈接貼在下面啦