RecyclerView源碼分析(三)--布局流程

閱讀本文您大概需要3.75分鐘。

相關(guān)系列文章

RecyclerView源碼分析(一)--整體設(shè)計(jì)

RecyclerView源碼分析(二)--測(cè)量流程

再上一篇文章中分析了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步:dispatchLayoutStep1dispatchLayoutStep2dispatchLayoutStep3。在之前自動(dòng)化Measure過程中我們?yōu)榱说玫紺hild的邊界值,使用了dispatchLayoutStep1dispatchLayoutStep2,所以在dispatchLayout中分了三種情況進(jìn)行處理:

  1. 沒有執(zhí)行過布局流程的情況
  2. 執(zhí)行過布局流程,但是之后size又有變化的情況
  3. 執(zhí)行過布局流程,可以直接使用之前數(shù)據(jù)的情況

不過,無論什么情況,最終都是完成dispatchLayoutStep1dispatchLayoutStep2dispatchLayoutStep3這三步,這樣的情況區(qū)分只是為了避免重復(fù)計(jì)算。接下按步分析。

dispatchLayoutStep1分析

dispatchLayoutStep1的主要作用有以下幾點(diǎn):

  1. 處理Adapter的更新
  2. 決定哪些動(dòng)畫播放
  3. 保存當(dāng)前View的信息
  4. 如果有必要的話再進(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é)一下我們知道了什么:

  1. RecyclerView并沒有對(duì)內(nèi)部的View進(jìn)行布局。而是交給LayoutManager去做具體的布局操作,因此才會(huì)有LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager這樣靈活的布局。而且我們可以通過實(shí)現(xiàn)onLayoutChildren,自定義LayoutManager。
  2. RecyclerView的布局過程中包含很多動(dòng)畫相關(guān)的處理。
  3. RecyclerView的數(shù)據(jù)改變的動(dòng)畫是在布局過程的第三步中統(tǒng)一觸發(fā)的。并不是一調(diào)用notify之后立即觸發(fā)。

今天省略了很多內(nèi)容,那些都是和動(dòng)畫相關(guān)的處理,下一篇就來分析,notify到動(dòng)畫播放的流程。敬請(qǐng)期待!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。