閱讀本文您大概需要3.75分鐘。
相關(guān)系列文章
再上一篇文章中分析了RecyclerView的Measure過程。在Measure過程中的自動(dòng)化Measure中,應(yīng)用過布局流程的,得到Child的邊界值,但是當(dāng)時(shí)我們略過了,那么今天接著分析RecyclerView的布局過程。
PS:源碼版本為24.1.1,如果下面與你的源碼有出入,請(qǐng)核實(shí)版本是否相同。
RecyclerView的Layout過程
首先貼一下源碼,不同版本的RecyclerView源碼會(huì)不同,如果你也打開了源碼,請(qǐng)確定源碼版本是否一致。
@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;
}
其實(shí)這里貌似值得分析的就是dispatchLayout();
這一句。去看一下dispatchLayout();
的源碼。
void dispatchLayout() {
……
if (mState.mLayoutStep == State.STEP_START) {
// 1) 沒有執(zhí)行過布局流程的情況
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates()
|| mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// 2) 執(zhí)行過布局流程,但是之后size又有變化的情況
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// 3) 執(zhí)行過布局流程,可以直接使用之前數(shù)據(jù)的情況
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
我們首先忽略各種判斷條件,可以明顯看出RecyclerView的布局過程分為3步:dispatchLayoutStep1
,dispatchLayoutStep2
和dispatchLayoutStep3
。在之前自動(dòng)化Measure過程中我們?yōu)榱说玫紺hild的邊界值,使用了dispatchLayoutStep1
和dispatchLayoutStep2
,所以在dispatchLayout
中分了三種情況進(jìn)行處理:
- 沒有執(zhí)行過布局流程的情況
- 執(zhí)行過布局流程,但是之后size又有變化的情況
- 執(zhí)行過布局流程,可以直接使用之前數(shù)據(jù)的情況
不過,無論什么情況,最終都是完成dispatchLayoutStep1
,dispatchLayoutStep2
和dispatchLayoutStep3
這三步,這樣的情況區(qū)分只是為了避免重復(fù)計(jì)算。接下按步分析。
dispatchLayoutStep1分析
dispatchLayoutStep1的主要作用有以下幾點(diǎn):
- 處理Adapter的更新
- 決定哪些動(dòng)畫播放
- 保存當(dāng)前View的信息
- 如果有必要的話再進(jìn)行上一布局操作,并保存它的信息
然后請(qǐng)?jiān)敿?xì)看注釋。
private void dispatchLayoutStep1() {
…… // 省略代碼,該部分判斷狀態(tài)和更改狀態(tài)以及保存一些信息
// 下面這個(gè)方法很重要,那么我們先略過,看下下面的內(nèi)容。哎~我就這么調(diào)皮!哈哈,
// 其實(shí)是,在沒有講動(dòng)畫流程之前,根本講不清。這個(gè)是動(dòng)畫流程的中間過程。所以
// ,在這里只要先知道,這里是處理Adapter更新,并計(jì)算動(dòng)畫類型的即可。
processAdapterUpdatesAndSetAnimationFlags();
…… // 設(shè)置一些狀態(tài),保存一些信息。
// 下面的內(nèi)容是需要運(yùn)行動(dòng)畫的情況下進(jìn)行的,主要做的事情就是找出那些要需要進(jìn)
// 行上一布局操作的ViewHolder,并且保存它們的邊界信息。如果有更新操作(這個(gè)更新
// 指的是內(nèi)容的更新,不是插入刪除的這種更新),然后保存這些更新的ViewHolder
if (mState.mRunSimpleAnimations) {
…… // 看上面的解釋,這里代碼都是和動(dòng)畫相關(guān)的,暫時(shí)懶得放,太占地方
}
// 下面的內(nèi)容是需要在布局結(jié)束之后運(yùn)行動(dòng)畫的情況下執(zhí)行的。主要做的事情就是
// 執(zhí)行上一布局操作,上一布局操作其實(shí)就是先以上一次的狀態(tài)執(zhí)行一邊LayoutManager
// 的onLayoutChildren方法,其實(shí)RecyclerView的布局策略就是在
// LayoutManager的onLayoutChildren方法中。執(zhí)行一次它就獲得了所有
// ViewHolder的邊界信息。只不過,這次獲得的是之前狀態(tài)下的ViewHolder的
// 邊界信息。不過這個(gè)應(yīng)該是要在LayoutManager中,根據(jù)state的isPreLayout
// 的返回值,選擇使用新的還是舊的position。但我在系統(tǒng)給的幾個(gè)LayoutManager中
// 都沒有看到。
if (mState.mRunPredictiveAnimations) {
……
mLayout.onLayoutChildren(mRecycler, mState);
……
}
…… //恢復(fù)狀態(tài)
}
嗯……,復(fù)雜的都省略了,感覺真是太爽了。
dispatchLayoutStep2分析
一路省略到了第二步,哈哈!dispatchLayoutStep2的主要作用有以下一點(diǎn),注意是一點(diǎn):
真正的布局!
private void dispatchLayoutStep2() {
…… // 設(shè)置狀態(tài)
mState.mInPreLayout = false; // 更改此狀態(tài),確保不是會(huì)執(zhí)行上一布局操作
// 真正布局就是這一句話,布局的具體策略交給了LayoutManager,哈哈!這篇的主角講完了!
mLayout.onLayoutChildren(mRecycler, mState);
…… // 設(shè)置和恢復(fù)狀態(tài)
}
dispatchLayoutStep3分析
為了表明我是一個(gè)良心作者,我還是 堅(jiān)持 把 對(duì)布局流程并沒有什么卵用的 布局第三步 分析一下!
第三步主要做的事情就是:
保存信息,觸發(fā)動(dòng)畫,清除垃圾
private void dispatchLayoutStep3() {
…… // 設(shè)置狀態(tài)
if (mState.mRunSimpleAnimations) {
…… // 需要?jiǎng)赢嫷那闆r。找出ViewHolder現(xiàn)在的位置,并且處理改變動(dòng)畫。最后觸發(fā)動(dòng)畫。
}
…… // 清除狀態(tài)和清除無用的信息
mLayout.onLayoutCompleted(mState); // 給LayoutManager的布局完成的回調(diào)
…… // 清除狀體和清楚無用的信息,最后在恢復(fù)一些信息信息,比如焦點(diǎn)。
}
又一個(gè)十分復(fù)雜的函數(shù)讓我給分析完了。
總結(jié)
這個(gè)源碼分析的也是云里霧里,但是我們總結(jié)一下我們知道了什么:
- RecyclerView并沒有對(duì)內(nèi)部的View進(jìn)行布局。而是交給LayoutManager去做具體的布局操作,因此才會(huì)有LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager這樣靈活的布局。而且我們可以通過實(shí)現(xiàn)onLayoutChildren,自定義LayoutManager。
- RecyclerView的布局過程中包含很多動(dòng)畫相關(guān)的處理。
- RecyclerView的數(shù)據(jù)改變的動(dòng)畫是在布局過程的第三步中統(tǒng)一觸發(fā)的。并不是一調(diào)用notify之后立即觸發(fā)。
今天省略了很多內(nèi)容,那些都是和動(dòng)畫相關(guān)的處理,下一篇就來分析,notify到動(dòng)畫播放的流程。敬請(qǐng)期待!!