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

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

相關(guān)系列文章

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

在上一篇文章中主要講解了RecyclerView內(nèi)部的大體設(shè)計(jì)結(jié)構(gòu)。因?yàn)槭菑慕Y(jié)構(gòu)出發(fā),所以多少對(duì)細(xì)節(jié)講的云里霧里。那么從這一篇開始要落實(shí)到代碼細(xì)節(jié)中了。

既然RecyclerView是一個(gè)GroupView,那么也就是一個(gè)View。那么View的繪制過程是measure到layout到draw的一個(gè)順序。然而一個(gè)GroupView的目的是盛放其它View的,那么最主要的還是其measure和layout過程。那么我們今天就來看看RecyclerView的measure過程。

PS:源碼版本為24.1.1,如果下面與你的源碼有出入,請(qǐng)核實(shí)版本是否相同。

RecyclerView的Measure過程

如果你這個(gè)時(shí)候也打開了源碼,你應(yīng)該會(huì)發(fā)現(xiàn)RecyclerView的onMeasure方法很長(zhǎng)。那么我們先將其分情況討論。

  1. 沒有LayoutManager的情況
  2. 有LayoutManager并開啟了自動(dòng)測(cè)量
  3. 有LayoutManager但沒有開啟自動(dòng)測(cè)量
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        // 1. 沒有LayoutManager的情況
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    if (mLayout.mAutoMeasure) {
        // 2. 有LayoutManager并開啟了自動(dòng)測(cè)量
        ……
    } else {
        // 3. 有LayoutManager但沒有開啟自動(dòng)測(cè)量
        ……
    }
}

第一種情況十分簡(jiǎn)單,就是執(zhí)行了defaultOnMeasure方法。里面就是計(jì)算并設(shè)置了RecyclerView的長(zhǎng)寬值。下來我們介紹另外兩種情況。

RecyclerView的自動(dòng)測(cè)量過程

在RecyclerView的早期版本,當(dāng)你為其設(shè)置了wrap_content值,但在其中的內(nèi)容改變的時(shí)候,RecyclerView并不能改變其大小來適應(yīng)內(nèi)部的內(nèi)容。因此后來加入了自動(dòng)測(cè)量機(jī)制,來解決這個(gè)問題。而且現(xiàn)在我們常用的三個(gè)LayoutManager,在其構(gòu)造函數(shù)中,均已經(jīng)設(shè)置了開啟自動(dòng)測(cè)量。所以我們現(xiàn)在可以放心大膽的為RecyclerView設(shè)置wrap_content。

那么我們來看一下RecyclerView的自動(dòng)測(cè)繪過程:

protected void onMeasure(int widthSpec, int heightSpec) {
    ……
    if (mLayout.mAutoMeasure) {
        // 第一部分:
        // 1) 首先執(zhí)行LayoutManager的onMeasure方法。
        // 2) 檢查如果width和height都已經(jīng)是精確值,那么就不用再根據(jù)內(nèi)容進(jìn)行計(jì)算所
        // 需要的width和height,那么跳過之后的步驟。如果有其中任何一個(gè)值不是精確值,
        // 則進(jìn)入到下面計(jì)算所需長(zhǎng)寬的步驟。
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                && heightMode == MeasureSpec.EXACTLY;
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        if (skipMeasure || mAdapter == null) {
            return;
        }
        // 第二部分:
        // 1) 開啟布局流程計(jì)算出所有Child的邊界
        // 2) 然后根據(jù)計(jì)算出的Child的邊界計(jì)算出RecyclerView的所需width和height
        // 3) 檢查是否需要再次測(cè)量
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
        }
        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;
        dispatchLayoutStep2();

        // 布局過程結(jié)束,根據(jù)Children中的邊界信息計(jì)算并設(shè)置RecyclerView長(zhǎng)寬的測(cè)量值
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        
        // 檢查是否需要再此測(cè)量。如果RecyclerView仍然有非精確的寬和高,或者這里還有至
        // 少一個(gè)Child還有非精確的寬和高,我們就需要在此測(cè)量。
        if (mLayout.shouldMeasureTwice()) {
            mLayout.setMeasureSpecs(
                    MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        }
    } else {
        ……
    }
}

自動(dòng)測(cè)繪過程可以分為兩部分:

第一部分:

  1. 首先執(zhí)行LayoutManager的onMeasure方法。
  2. 檢查如果width和height都已經(jīng)是精確值,那么就不用再根據(jù)內(nèi)容進(jìn)行計(jì)算所需要的width和height,那么跳過之后的步驟。如果有其中任何一個(gè)值不是精確值,則進(jìn)入到下面計(jì)算所需長(zhǎng)寬的步驟。

第二部分:

  1. 開啟布局流程,計(jì)算出所有Child的邊界。
  2. 然后根據(jù)計(jì)算出的Child的邊界計(jì)算出RecyclerView的所需width和height,并設(shè)置。
  3. 檢查是否需要再次測(cè)量,如果需要?jiǎng)t在此進(jìn)行測(cè)量。

注意:
因?yàn)樽詣?dòng)繪制過程中執(zhí)行了布局流程,那么在之后布局的時(shí)候會(huì)檢查是否已經(jīng)進(jìn)行過布局流程,如果已經(jīng)進(jìn)行過布局流程,則會(huì)跳過進(jìn)行過的布局流程,不會(huì)造成重復(fù)操作。(布局流程有三步)

RecyclerView的非自動(dòng)測(cè)繪流程

當(dāng)我們使用系統(tǒng)提供的那三個(gè)LayoutManager的時(shí)候,默認(rèn)是開啟自動(dòng)測(cè)繪的。除非,你在初始化LayoutManager之后,自己通過setAutoMeasureEnabled(false)方法設(shè)置成false。不然不會(huì)走到非自動(dòng)測(cè)繪流程的。但是如果我們是使用的自己自定義的LayoutManager,而且我們自定義的LayoutManager又沒有在初始化的時(shí)候開啟自動(dòng)測(cè)繪,那么默認(rèn)將會(huì)是不開啟自動(dòng)測(cè)繪。這個(gè)時(shí)候會(huì)走到非自動(dòng)測(cè)繪流程。

那么接下來看一看非自動(dòng)測(cè)繪流程。

protected void onMeasure(int widthSpec, int heightSpec) {
    ……
    if (mLayout.mAutoMeasure) {
        ……
    } else {
        // 第一部分:如果RecyclerView已經(jīng)設(shè)置了Size固定,則執(zhí)行LayoutManager的onMeasure方法
        if (mHasFixedSize) {
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            return;
        }
        // 第二部分:
        // 1) 如果在測(cè)量的過程中有數(shù)據(jù)有更新,則先處理更新的數(shù)據(jù)
        // 2) 執(zhí)行自定義測(cè)量流程,這需要自定義的LayoutManager實(shí)現(xiàn)onMeasure方法。
        if (mAdapterUpdateDuringMeasure) {
            eatRequestLayout();
            processAdapterUpdatesAndSetAnimationFlags();

            if (mState.mRunPredictiveAnimations) {
                mState.mInPreLayout = true;
            } else {
                // consume remaining updates to provide a consistent state with the layout pass.
                mAdapterHelper.consumeUpdatesInOnePass();
                mState.mInPreLayout = false;
            }
            mAdapterUpdateDuringMeasure = false;
            resumeRequestLayout(false);
        }
        // 處理完新更新的數(shù)據(jù),然后執(zhí)行自定義測(cè)量操作。
        if (mAdapter != null) {
            mState.mItemCount = mAdapter.getItemCount();
        } else {
            mState.mItemCount = 0;
        }
        eatRequestLayout();
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        resumeRequestLayout(false);
        mState.mInPreLayout = false; // clear
    }
}

RecyclerView的非自動(dòng)化測(cè)繪流程也可以分為兩部分:

第一部分:
在固定尺寸模式下,直接執(zhí)行LayoutManager的onMeasure方法。

第二部分:
不在固定尺寸模式下

  1. 如果在測(cè)量的過程中有數(shù)據(jù)有更新,則先處理更新的數(shù)據(jù)
  2. 執(zhí)行自定義測(cè)量流程,這需要自定義的LayoutManager實(shí)現(xiàn)onMeasure方法。

總結(jié)

一般情況下,進(jìn)入的都是自動(dòng)測(cè)量模式。

最不常見的是沒有設(shè)置LayoutManager的模式。

最后需要注意的是:我們自定義LayoutManager的時(shí)候要根據(jù)自己的需求,決定是否要開啟自動(dòng)測(cè)量。如果需要開啟,則要主動(dòng)調(diào)用setAutoMeasureEnabled(true),否則默認(rèn)是不開啟的。

感謝您的閱讀,如果您覺得本文對(duì)你有所幫助,請(qǐng)不要吝嗇您的喜歡,您的喜歡是我寫作的動(dòng)力。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容