RecyclerView原理


在分析RecyclerView源碼之前,我們還是對RecyclerView有一個初步的了解,簡單的了解它是什么,它的基本結構有哪些。


RecyclerView是Google爸爸在2014年的IO大會提出來(看來RecyclerView的年齡還是比較大了😂),具體目的是不是用來替代ListView的,樓主也不知道,因為那時候樓主還在讀高二。但是在實際開發中,自從有了RecyclerView,ListView和GridView就很少用了,所以我們暫且認為RecyclerView的目的是替代ListView和GridView。


RecyclerView本身是一個展示大量數據的控件,相比較ListView,RecyclerView的4級緩存(也有人說是3級緩存,這些都不重要😂)就表現的非常出色,在性能方面相比於ListView提升了不少。同時由於LayoutManager的存在,讓RecyclerView不僅有ListView的特點,同時兼有GridView的特點。這可能是RecyclerView受歡迎的原因之一吧。


RecyclerView在設計方面上也是非常的靈活,不同的部分承擔着不同的職責。其中Adapter負責提供數據,包括創建ViewHolder和綁定數據,LayoutManager負責ItemView的測量和布局,ItemAnimator負責每個ItemView的動畫,ItemDecoration負責每個ItemView的間隙。這種插拔式的架構使得RecyclerView變得非常的靈活,每一個人都可以根據自身的需求來定義不同的部分。


正因為這種插拔式的設計,使得RecyclerView在使用上相比較於其他的控件稍微難那么一點點,不過這都不算事,誰叫RecyclerView這么惹人愛呢😂。

 

本文參考文章:

 

進階RecyclerView源碼解析(一)——繪制流程

https://www.jianshu.com/p/c52b947fe064

 

RecyclerView剖析

https://blog.csdn.net/qq_23012315/article/details/50807224

 

RecyclerView剖析——續

https://blog.csdn.net/qq_23012315/article/details/51096696

 

注意,本文RecyclerView源碼均來自於27.1.1

 

/   measure   /

 

不管RecyclerView是多么神奇,它也是一個View,所以分析它的三大流程是非常有必要的。同時,如果了解過RecyclerView的同學應該都知道,RecyclerView的三大流程跟普通的View比較,有很大的不同。


首先,我們來看看measure過程,來看看RecyclerView的onMeasure方法。

 

protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        // 第一種情況
    }
    if (mLayout.isAutoMeasureEnabled()) {
        // 第二種情況
    } else {
        // 第三種情況
    }
}

 

onMeasure方法還是有點長,這里我將它分為3種情況,我將簡單解釋這三種情況。

 

mLayout即LayoutManager的對象。我們知道,當RecyclerView的LayoutManager為空時,RecyclerView不能顯示任何的數據,在這里我們找到答案。

 

LayoutManager開啟了自動測量時,這是一種情況。在這種情況下,有可能會測量兩次。

 

第三種情況就是沒有開啟自動測量的情況,這種情況比較少,因為為了RecyclerView支持warp_content屬性,系統提供的LayoutManager都開啟自動測量的,不過我們還是要分析的。

 

首先我們來第一種情況。

 

(1).當LayoutManager為空時

 

這種情況下比較簡單,我們來看看源碼:

 

if (mLayout == null) {
    defaultOnMeasure(widthSpec, heightSpec);
    return;
}

 

直接調了defaultOnMeasure方法,我們繼續來看defaultOnMeasure方法。

 

void defaultOnMeasure(int widthSpec, int heightSpec) {
    // calling LayoutManager here is not pretty but that API is already public and it is better
    // than creating another method since this is internal.
    final int width = LayoutManager.chooseSize(widthSpec,
            getPaddingLeft() + getPaddingRight(),
            ViewCompat.getMinimumWidth(this));
    final int height = LayoutManager.chooseSize(heightSpec,
            getPaddingTop() + getPaddingBottom(),
            ViewCompat.getMinimumHeight(this));

    setMeasuredDimension(width, height);
}

 

在defaultOnMeasure方法里面,先是通過LayoutManager的chooseSize方法來計算值,然后就是setMeasuredDimension方法來設置寬高。我們來看看:

 

public static int chooseSize(int spec, int desired, int min) {
    final int mode = View.MeasureSpec.getMode(spec);
    final int size = View.MeasureSpec.getSize(spec);
    switch (mode) {
        case View.MeasureSpec.EXACTLY:
            return size;
        case View.MeasureSpec.AT_MOST:
            return Math.min(size, Math.max(desired, min));
        case View.MeasureSpec.UNSPECIFIED:
        default:
            return Math.max(desired, min);
    }
}

 

chooseSize方法表達的意思比較簡單,就是通過RecyclerView的測量mode來獲取不同的值,這里就不詳細的解釋了。


到此,第一種情況就分析完畢了。因為當LayoutManager為空時,那么當RecyclerView處於onLayout階段時,會調用dispatchLayout方法。而在dispatchLayout方法里面有這么一行代碼:

 

if (mLayout == null) {
    Log.e(TAG, "No layout manager attached; skipping layout");
    // leave the state in START
    return;
}

 

所以,當LayoutManager為空時,不顯示任何數據是理所當然的。

 

現在我們來看看第二種情況,也就是正常的情況。

 

(2). 當LayoutManager開啟了自動測量

 

在分析這種情況之前,我們先對了解幾個東西。


RecyclerView的測量分為兩步,分別調用dispatchLayoutStep1和dispatchLayoutStep2。同時,了解過RecyclerView源碼的同學應該知道在RecyclerView的源碼里面還一個dispatchLayoutStep3方法。這三個方法的方法名比較接近,所以容易讓人搞混淆。本文會詳細的講解這三個方法的作用。


由於在這種情況下,只會調用dispatchLayoutStep1和dispatchLayoutStep2這兩個方法,所以這里會重點的講解這兩個方法。而dispatchLayoutStep3方法的調用在RecyclerView的onLayout方法里面,所以在后面分析onLayout方法時再來看dispatchLayoutStep3方法。


我們在分析之前,先來看一個東西--mState.mLayoutStep。這個變量有幾個取值情況。我們分別來看看:

 

 

從上表中,我們了解到mState.mLayoutStep的三個狀態對應着不同的dispatchLayoutStep方法。這一點,我們必須清楚,否則接下來的代碼將難以理解。

 

好了,前戲准備的差不多,現在應該進入高潮了😂。我們開始正式的分析源碼了。

 

if (mLayout.isAutoMeasureEnabled()) {
    final int widthMode = MeasureSpec.getMode(widthSpec);
    final int heightMode = MeasureSpec.getMode(heightSpec);

    /**
     * This specific call should be considered deprecated and replaced with
     * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
     * break existing third party code but all documentation directs developers to not
     * override {@link LayoutManager#onMeasure(int, int)} when
     * {@link LayoutManager#isAutoMeasureEnabled()} returns true.
     */
    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

    final boolean measureSpecModeIsExactly =
            widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
    if (measureSpecModeIsExactly || mAdapter == null) {
        return;
    }

    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
    }
    // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
    // consistency
    mLayout.setMeasureSpecs(widthSpec, heightSpec);
    mState.mIsMeasuring = true;
    dispatchLayoutStep2();

    // now we can get the width and height from the children.
    mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

    // if RecyclerView has non-exact width and height and if there is at least one child
    // which also has non-exact width & height, we have to re-measure.
    if (mLayout.shouldMeasureTwice()) {
        mLayout.setMeasureSpecs(
                MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
        mState.mIsMeasuring = true;
        dispatchLayoutStep2();
        // now we can get the width and height from the children.
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
    }
}

 

我將這段代碼分為三步。我們來看看:

 

調用LayoutManager的onMeasure方法進行測量。對於onMeasure方法,我也感覺到非常的迷惑,發現傳統的LayoutManager都沒有實現這個方法。后面,我們會將簡單的看一下這個方法。

 

如果mState.mLayoutStep為State.STEP_START的話,那么就會執行dispatchLayoutStep1方法,然后會執行dispatchLayoutStep2方法。

 

如果需要第二次測量的話,會再一次調用dispatchLayoutStep2 方法。

 

以上三步,我們一步一步的來分析。首先,我們來看看第一步,也是看看onMeasure方法。

 

LayoutManager的onMeasure方法究竟為我們做什么,我們來看看:

 

public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
    mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}

 

默認是調用的RecyclerView的defaultOnMeasure方法,至於defaultOnMeasure方法里面究竟做了什么,這在前面已經介紹過了,這里就不再介紹了。

View的onMeasure方法的作用通常來說有兩個。一是測量自身的寬高,從RecyclerView來看,它將自己的測量工作托管給了LayoutManager的onMeasure方法。所以,我們在自定義LayoutManager時,需要注意onMeasure方法存在,不過從官方提供的幾個LayoutManager,都沒有重寫這個方法。所以不到萬得已,最好不要重寫LayoutManager的onMeasure方法;二是測量子View,不過到這里我們還沒有看到具體的實現。

接下來,我們來分析第二步,看看dispatchLayoutStep1方法和dispatchLayoutStep2方法究竟做了什么。

在正式分析第二步之前,我們先對這三個方法有一個大概的認識。

 

 

我們回到onMeasure方法里面,先看看整個執行過程。

 

if (mState.mLayoutStep == State.STEP_START) {
    dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();

 

如果mState.mLayoutStep == State.STEP_START時,才會調用 dispatchLayoutStep1方法,這里與我們前面介紹mLayoutStep對應起來了。現在我們看看dispatchLayoutStep1方法

 

private void dispatchLayoutStep1() {
    mState.assertLayoutStep(State.STEP_START);
    fillRemainingScrollValues(mState);
    mState.mIsMeasuring = false;
    startInterceptRequestLayout();
    mViewInfoStore.clear();
    onEnterLayoutOrScroll();
    processAdapterUpdatesAndSetAnimationFlags();
    saveFocusInfo();
    mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
    mItemsAddedOrRemoved = mItemsChanged = false;
    mState.mInPreLayout = mState.mRunPredictiveAnimations;
    mState.mItemCount = mAdapter.getItemCount();
    findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);

    if (mState.mRunSimpleAnimations) {
       // 找到沒有被remove的ItemView,保存OldViewHolder信息,准備預布局
    }
    if (mState.mRunPredictiveAnimations) {
       // 進行預布局
    } else {
        clearOldPositions();
    }
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    mState.mLayoutStep = State.STEP_LAYOUT;
}

 

本文只簡單分析一下這個方法,因為這個方法跟ItemAnimator有莫大的關系,后續在介紹ItemAnimator時會詳細的分析。在這里,我們將重點放在processAdapterUpdatesAndSetAnimationFlags里面,因為這個方法計算了mRunSimpleAnimations和mRunPredictiveAnimations。

 

private void processAdapterUpdatesAndSetAnimationFlags() {
    if (mDataSetHasChangedAfterLayout) {
        // Processing these items have no value since data set changed unexpectedly.
        // Instead, we just reset it.
        mAdapterHelper.reset();
        if (mDispatchItemsChangedEvent) {
            mLayout.onItemsChanged(this);
        }
    }
    // simple animations are a subset of advanced animations (which will cause a
    // pre-layout step)
    // If layout supports predictive animations, pre-process to decide if we want to run them
    if (predictiveItemAnimationsEnabled()) {
        mAdapterHelper.preProcess();
    } else {
        mAdapterHelper.consumeUpdatesInOnePass();
    }
    boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
    mState.mRunSimpleAnimations = mFirstLayoutComplete
            && mItemAnimator != null
            && (mDataSetHasChangedAfterLayout
            || animationTypeSupported
            || mLayout.mRequestedSimpleAnimations)
            && (!mDataSetHasChangedAfterLayout
            || mAdapter.hasStableIds());
    mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
            && animationTypeSupported
            && !mDataSetHasChangedAfterLayout
            && predictiveItemAnimationsEnabled();
}

 

這里我們的重心放在mFirstLayoutComplete變量里面,我們發現mRunSimpleAnimations的值與mFirstLayoutComplete有關,mRunPredictiveAnimations同時跟mRunSimpleAnimations有關。所以這里我們可以得出一個結論,當RecyclerView第一次加載數據時,是不會執行的動畫。換句話說,每個ItemView還沒有layout完畢,怎么會進行動畫。這一點,我們也可以通過Demo來證明,這里也就不展示了。

 

接下來我們看看dispatchLayoutStep2方法,這個方法是真正布局children。我們來看看:

 

private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout
    mState.mInPreLayout = false;
    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);
}

 

在這里,我們重點的看兩行代碼。一是在這里,我們可以看到Adapter的getItemCount方法被調用;二是調用了LayoutManager的onLayoutChildren方法,這個方法里面進行對children的測量和布局,同時這個方法也是這里的分析重點。


系統的LayoutManager的onLayoutChildren方法是一個空方法,所以需要LayoutManager的子類自己來實現。從這里,我們可以得出兩個點。

 

1. 子類LayoutManager需要自己實現onLayoutChildren方法,從而來決定RecyclerView在該LayoutManager的策略下,應該怎么布局。從這里,我們看出來RecyclerView的靈活性。

 

2. LayoutManager類似於ViewGroup,將onLayoutChildren方法(ViewGroup是onLayout方法)公開出來,這種模式在Android中很常見的。

 

這里,我先不對onLayoutChildren方法進行展開,待會會詳細的分析。

 

接下來,我們來分析第三種情況--沒有開啟自動測量

 

(3).沒有開啟自動測量

 

我們先來看看這一塊的代碼。

 

if (mHasFixedSize) {
    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
    return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    processAdapterUpdatesAndSetAnimationFlags();
    onExitLayoutOrScroll();

    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;
    stopInterceptRequestLayout(false);
else if (mState.mRunPredictiveAnimations) {
    // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
    // this means there is already an onMeasure() call performed to handle the pending
    // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
    // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
    // because getViewForPosition() will crash when LM uses a child to measure.
    setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
    return;
}

if (mAdapter != null) {
    mState.mItemCount = mAdapter.getItemCount();
else {
    mState.mItemCount = 0;
}
startInterceptRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
stopInterceptRequestLayout(false);
mState.mInPreLayout = false; // clear

 

例如上面的代碼,我將分為2步:

 

1. 如果mHasFixedSize為true(也就是調用了setHasFixedSize方法),將直接調用LayoutManager的onMeasure方法進行測量。

 

2. 如果mHasFixedSize為false,同時此時如果有數據更新,先處理數據更新的事務,然后調用LayoutManager的onMeasure方法進行測量

 

通過上面的描述,我們知道,如果未開啟自動測量,那么肯定會調用LayoutManager的onMeasure方法來進行測量,這就是LayoutManager的onMeasure方法的作用。

 

至於onMeasure方法怎么進行測量,那就得看LayoutManager的實現類。在這里,我們就不進行深入的追究了。

 

/   layout   /

 

measure過程分析的差不多了,接下來我們就該分析第二個過程--layout。我們來看看onLayout方法:

 

@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;
}

 

onLayout方法本身沒有做多少的事情,重點還是在dispatchLayout方法里面。

 

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();
}

 

dispatchLayout方法也是非常的簡單,這個方法保證RecyclerView必須經歷三個過程--dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3。


同時,在后面的文章中,你會看到dispatchLayout方法其實還為RecyclerView節省了很多步驟,也就是說,在RecyclerView經歷一次完整的dispatchLayout之后,后續如果參數有所變化時,可能只會經歷最后的1步或者2步。當然這些都是后話了😂。


對於dispatchLayoutStep1和dispatchLayoutStep2方法,我們前面已經講解了,這里就不做過多的解釋了。這里,我們就簡單的看一下dispatchLayoutStep3方法吧。

 

private void dispatchLayoutStep3() {
    // ······
    mState.mLayoutStep = State.STEP_START;
    // ······
}

 

為什么這里只是簡單看一下dispatchLayoutStep3方法呢?因為這個方法主要是做Item的動畫,也就是我們熟知的ItemAnimator的執行,而本文不對動畫進行展開,所以先省略動畫部分。


在這里,我們需要關注dispatchLayoutStep3方法的是,它將mLayoutStep重置為了State.STEP_START。也就是說如果下一次重新開始dispatchLayout的話,那么肯定會經歷dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3三個方法。


以上就是RecyclerView的layout過程,是不是感覺非常的簡單?RecyclerView跟其他ViewGroup不同的地方在於,如果開啟了自動測量,在measure階段,已經將Children布局完成了;如果沒有開啟自動測量,則在layout階段才布局Children。

 

/   draw   /

 

接下來,我們來分析三大流程的最后一個階段--draw。在正式分析draw過程之前,我先來對RecyclerView的draw做一個概述。

 

RecyclerView分為三步,我們來看看:

 

1. 調用super.draw方法。這里主要做了兩件事:1. 將Children的繪制分發給ViewGroup;2. 將分割線的繪制分發給ItemDecoration。

 

2. 如果需要的話,調用ItemDecoration的onDrawOver方法。通過這個方法,我們在每個ItemView上面畫上很多東西。

 

3. 如果RecyclerView調用了setClipToPadding,會實現一種特殊的滑動效果--每個ItemView可以滑動到padding區域

 

我們來看看這部分的代碼:

 

public void draw(Canvas c) {
    // 第一步
    super.draw(c);
    // 第二步
    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) {
        mItemDecorations.get(i).onDrawOver(c, this, mState);
    }
    // 第三步
    // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we
    // need find children closest to edges. Not sure if it is worth the effort.
    // ······
}

 

熟悉三大流程的同學,肯定知道第一步會回調到onDraw方法里面,也就是說關於Children的繪制和ItemDecoration的繪制,是在onDraw方法里面。

 

@Override
public void onDraw(Canvas c) {
    super.onDraw(c);

    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) {
        mItemDecorations.get(i).onDraw(c, this, mState);
    }
}

 

onDraw方法是不是非常的簡單?調用super.onDraw方法將Children的繪制分發給ViewGroup執行;然后將ItemDecoration的繪制分發到ItemDecoration的onDraw方法里面去。從這里,我們可以看出來,RecyclerView的設計實在是太靈活了!


至於其余兩步都比較簡單,這里就不詳細分析了。不過,從這里,我們終於明白了ItemDecoration的onDraw方法和onDrawOver方法的區別。

 

/   onLayoutChildren方法   /

 

從整體來說,RecyclerView的三大流程還是比較簡單,不過在整個過程中,我們似乎忽略了一個過程--那就是RecyclerView到底是怎么layout children的?


前面在介紹dispatchLayoutStep2方法時,只是簡單的介紹了,RecyclerView通過調用LayoutManager的onLayoutChildren方法。LayoutManager本身對這個方法沒有進行實現,所以必須得看看它的子類,這里我們就來看看LinearLayoutManager。


由於LinearLayoutManager的onLayoutChildren方法比較長,這里不可能貼出完整的代碼,所以這里我先對這個方法做一個簡單的概述,方便大家理解。

 

1. 確定錨點的信息,這里面的信息包括:1.Children的布局方向,有start和end兩個方向;2. mPosition和mCoordinate,分別表示Children開始填充的position和坐標。

 

2. 調用detachAndScrapAttachedViews方法,detach掉或者remove掉RecyclerView的Children。這一點本來不在本文的講解范圍內,但是為了后續對RecyclerView的緩存機制有更好的了解,這里特別的提醒一下。

 

3. 根據錨點信息,調用fill方法進行Children的填充。這個過程中根據錨點信息的不同,可能會調用兩次fill方法。

 

接下來,我們看看代碼:

 

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
    // ······
    // 第一步
    final View focused = getFocusedChild();
    if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
            || mPendingSavedState != null) {
        mAnchorInfo.reset();
        mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
        // calculate anchor position and coordinate
        updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
        mAnchorInfo.mValid = true;
    }
    // ······
    // 第二步
    detachAndScrapAttachedViews(recycler);
    mLayoutState.mIsPreLayout = state.isPreLayout();
    // 第三步
    if (mAnchorInfo.mLayoutFromEnd) {
        // fill towards start
        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;
        }
        // fill towards end
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtra = 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.mExtra = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
        }
    } else {
        // fill towards end
        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;
        }
        // fill towards start
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtra = extraForStart;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;

        if (mLayoutState.mAvailable > 0) {
            extraForEnd = mLayoutState.mAvailable;
            // start could not consume all it should. add more items towards end
            updateLayoutStateToFillEnd(lastElement, endOffset);
            mLayoutState.mExtra = extraForEnd;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
        }
    }
    // ······
}

 

相信從上面的代碼都可以找出每一步的執行。現在,我們來詳細分析每一步。首先來看第一步--確定錨點的信息


要想看錨點信息的計算過程,我們可以從updateAnchorInfoForLayout方法里面來找出答案,我們來看看updateAnchorInfoForLayout方法:

 

private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
        AnchorInfo anchorInfo) {
    // 第一種計算方式
    if (updateAnchorFromPendingData(state, anchorInfo)) {
        return;
    }
    // 第二種計算方式
    if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
        return;
    }
    // 第三種計算方式
    anchorInfo.assignCoordinateFromPadding();
    anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
}

 

我相信通過上面的代碼注釋,大家都能明白updateAnchorInfoForLayout方法到底干了嘛,這里我簡單分析一下這三種確定所做的含義,具體是怎么做的,這里就不討論,因為這里面的細節太多了,深入的討論容易將我們聰明無比的大腦搞暈😂。

 

1. 第一種計算方式,表示含義有兩種:1.RecyclerView被重建,期間回調了onSaveInstanceState方法,所以目的是為了恢復上次的布局;2.RecyclerView調用了scrollToPosition之類的方法,所以目的是讓RecyclerView滾到准確的位置上去。所以,錨點的信息根據上面的兩種情況來計算。

 

2. 第二種計算方法,從Children上面來計算錨點信息。這種計算方式也有兩種情況:1. 如果當前有擁有焦點的Child,那么有當前有焦點的Child的位置來計算錨點;2. 如果沒有child擁有焦點,那么根據布局方向(此時布局方向由mLayoutFromEnd來決定)獲取可見的第一個ItemView或者最后一個ItemView。

 

3. 如果前面兩種方式都計算失敗了,那么采用第三種計算方式,也就是默認的計算方式。

 

以上就是updateAnchorInfoForLayout方法所做的事情,這里就不詳細糾結每種計算方式的細節,有興趣的同學可以看看。


至於第二步,調用detachAndScrapAttachedViews方法對所有的ItemView進行回收,這部分的內容屬於RecyclerView緩存機制的部分,本文先在這里埋下一個伏筆,后續專門講解RecyclerView會詳細的分析它,所以這里就不講解了。


接下來我們來看看第三步,也就是調用fill方法來填充Children。在正式分析填充過程時,我們先來看一張圖片:

 

 

上圖形象的展現出三種fill的情況。其中,我們可以看到第三種情況,fill方法被調用了兩次。

我們看看fill方法:

 

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    // ······
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        // ······
        layoutChunk(recycler, state, layoutState, layoutChunkResult);

    }
     // ······
}

 

fill方法的代碼比較長,其實都是來計算可填充的空間,真正填充Child的地方是layoutChunk方法。我們來看看layoutChunk方法。


由於layoutChunk方法比較長,這里我就不完整的展示,為了方便理解,我對這個方法做一個簡單的概述,讓大家有一個大概的理解。

 

1. 調用LayoutState的next方法獲得一個ItemView。千萬別小看這個next方法,RecyclerView緩存機制的起點就是從這個方法開始,可想而知,這個方法到底為我們做了多少事情。

 

2. 如果RecyclerView是第一次布局Children的話(layoutState.mScrapList == null為true),會先調用addView,將View添加到RecyclerView里面去。

 

3. 調用measureChildWithMargins方法,測量每個ItemView的寬高。注意這個方法測量ItemView的寬高考慮到了兩個因素:1.margin屬性;2.ItemDecoration的offset。

 

4. 調用layoutDecoratedWithMargins方法,布局ItemView。這里也考慮上面的兩個因素的。

 

至於每一步具體干了嘛,這里就不詳細的解釋,都是一些基本操作,有興趣的同學可以看看。

綜上所述,便是LayoutManager的onLayoutChildren方法整個執行過程,思路還是比較簡單的。

 

/   總結   /

 

本文到此就差不多了,在最后,我做一個簡單的總結。

 

1. RecyclerView的measure過程分為三種情況,每種情況都有執行過程。通常來說,我們都會走自動測量的過程。

 

2. 自動測量里面需要分清楚mState.mLayoutStep狀態值,因為根據不同的狀態值調用不同的dispatchLayoutStep方法。

 

3. layout過程也根據mState.mLayoutStep狀態來調用不同的dispatchLayoutStep方法。

 

4. draw過程主要做了四件事:1. 繪制ItemDecoration的onDraw部分。2. 繪制Children。3. 繪制ItemDecoration的drawOver部分。4. 根據mClipToPadding的值來判斷是否進行特殊繪制。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM