Android源碼分析之RecyclerView源碼分析(一)——繪制流程

系列文章

  1. Android源碼分析之ListView源碼
  2. Android源碼分析之RecyclerView源碼分析(一)——繪制流程
  3. Android源碼分析之RecyclerView源碼分析(二)——緩存機(jī)制

前言

RecyclerView是Google在2014年的IO大會(huì)中提出來(lái)的,可以認(rèn)為是用來(lái)代替ListView的,其是support-v7包中的組件,但隨著AndroidX(谷歌對(duì) android.support.xxx 包的整理產(chǎn)物,因?yàn)橹皊upport包的管理較為混亂,所以谷歌推出了AndroidX)的出現(xiàn),我們需要將項(xiàng)目中對(duì)其的使用遷移到 AndroidX 上。RecyclerView的繼承關(guān)系如下圖所示:

RecyclerView繼承關(guān)系.png

相比ListView的兩級(jí)緩存,RecyclerView做到了四級(jí)緩存,而且整體上的架構(gòu)做到了解耦,每個(gè)模塊分別負(fù)責(zé)不同的功能實(shí)現(xiàn)。其中Adapter負(fù)責(zé)提供數(shù)據(jù),包括創(chuàng)建ViewHolder和綁定數(shù)據(jù),LayoutManager負(fù)責(zé)ItemView的測(cè)量和布局,ItemAnimator負(fù)責(zé)每個(gè)ItemView的動(dòng)畫(huà),這樣功能性的解耦讓RecyclerView使用十分方便,也方便了開(kāi)發(fā)者擴(kuò)展。這里需要提一下ViewHolder,對(duì)于Adapter來(lái)說(shuō),一個(gè)ViewHolder就對(duì)應(yīng)一個(gè)data。

有關(guān)RecyclerView的使用這里也不做過(guò)多分析,具體可以參考Android RecyclerView 使用完全解析。

繪制過(guò)程

類似ListView,RecyclerView本質(zhì)上還是一個(gè)View,因此在繪制的過(guò)程中還是分為以下三步:onMeasure、onLayout、onDraw,下面先看下其onMeasure方法:

繪制第一步:onMeasure方法

protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        // 情況1
    }
    
    if (mLayout.mAutoMeasure) {
        // 情況2
    } else {
        // 情況3
    }

onMeasure方法中主要是分了三種情況,情況1是mLayout(LayoutManager對(duì)象)為null,LayoutManager負(fù)責(zé)控制 RecyclerView 中 item 的測(cè)量和布局,當(dāng)LayoutManager為空時(shí),RecyclerView是不能顯示任何數(shù)據(jù)的(原因后續(xù)解釋)

另外兩種情況是mLayout對(duì)象不為空,第二種情況是LayoutManager開(kāi)啟了自動(dòng)測(cè)量,第三種情況是LayoutManager沒(méi)有開(kāi)啟自動(dòng)測(cè)量。

onMeasure情況1:LayoutManager為null

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

void defaultOnMeasure(int widthSpec, int heightSpec) {
    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);
}

這種情況下直接調(diào)用了defaultOnMeasure方法,該方法中通過(guò)LayoutManager.choose()方法來(lái)計(jì)算寬高值,然后調(diào)用setMeasuredDimension()設(shè)置寬高

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方法就是通過(guò)RecyclerView的不同測(cè)量模式來(lái)選取不同的值。

onMeasure情況2:LayoutManager開(kāi)啟了自動(dòng)測(cè)量

if (mLayout.mAutoMeasure) {
    // 先獲取測(cè)量模式
    final int widthMode = MeasureSpec.getMode(widthSpec);
    final int heightMode = MeasureSpec.getMode(heightSpec);
    // 是否可以跳過(guò)測(cè)量
    final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
            && heightMode == MeasureSpec.EXACTLY;
    // 調(diào)用LayoutManager.onMeasure方法測(cè)量
    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
    // 如果可以跳過(guò)測(cè)量或者adapter為null,則直接返回
    if (skipMeasure || mAdapter == null) {
        return;
    }
    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
    }
    
    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);
    }
}

這種情況下,首先調(diào)用LayoutManager.onMeasure方法測(cè)量,但是Android官方的三種LayoutManager(LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager)都沒(méi)有復(fù)寫(xiě)此方法,此方法源碼:

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

可以看到LayoutManager.onMeasure方法默認(rèn)是調(diào)用的RecyclerView的defaultOnMeasure方法,前面已經(jīng)介紹過(guò)此方法了。

接下來(lái)判斷mState.mLayoutStep這個(gè)變量,即當(dāng)前繪制狀態(tài),如果為State.STEP_START,那么便會(huì)執(zhí)行dispatchLayoutStep1方法,隨后又調(diào)用了dispatchLayoutStep2方法,最后如果需要二次測(cè)量的話,那么會(huì)再調(diào)用一次dispatchLayoutStep2方法。

ViewGroup的onMeasure方法的作用通常來(lái)說(shuō)有兩個(gè):一是測(cè)量自身的寬高,從RecyclerView來(lái)看,它將自己的測(cè)量工作托管給了LayoutManager的onMeasure方法。所以,我們?cè)谧远xLayoutManager時(shí),需要注意onMeasure方法的存在,不過(guò)官方提供的幾個(gè)LayoutManager,都沒(méi)有重寫(xiě)這個(gè)方法二是測(cè)量子View的寬高,不過(guò)到目前為止我們還沒(méi)有看到具體的實(shí)現(xiàn)。

摘自RecyclerView 源碼分析(一) - RecyclerView的三大流程

前面提到了mState.mLayoutStep這個(gè)變量,mStateRecyclerView中State類的對(duì)象,mLayoutStep有三個(gè)狀態(tài),其三個(gè)狀態(tài)正好和三個(gè)dispatchLayoutStep方法(還有一個(gè)dispatchLayoutStep3)一一對(duì)應(yīng)

State.mLayoutStep dispatchLayoutStep 含義說(shuō)明
STEP_START dispatchLayoutStep1 STEP_STARTState.mLayoutStep的默認(rèn)值,執(zhí)行完dispatchLayoutStep1后會(huì)將該狀態(tài)置為STEP_LAYOUT
STEP_LAYOUT dispatchLayoutStep2 表明處于layout階段,調(diào)用dispatchLayoutStep2對(duì)RecyclerView的子view進(jìn)行l(wèi)ayout,執(zhí)行完之后會(huì)將該狀態(tài)置為STEP_ANIMATIONS
STEP_ANIMATIONS dispatchLayoutStep3 表明處于執(zhí)行動(dòng)畫(huà)階段,調(diào)用dispatchLayoutStep3之后會(huì)將該狀態(tài)置再次變?yōu)?code>STEP_START

dispatchLayoutStep1方法

RecyclerView處于State.STEP_START狀態(tài)時(shí),會(huì)調(diào)用dispatchLayoutStep1方法,其源碼如下:

private void dispatchLayoutStep1() {
    
    ...
    
    processAdapterUpdatesAndSetAnimationFlags();
   
    ...

    if (mState.mRunSimpleAnimations) {
        // Step 0:找出所有未移除的ItemView,進(jìn)行預(yù)布局
    }
    if (mState.mRunPredictiveAnimations) {
        // Step 1:預(yù)布局
    } else {
        clearOldPositions();
    }
    onExitLayoutOrScroll();
    resumeRequestLayout(false);
    mState.mLayoutStep = State.STEP_LAYOUT;
}

可以看到dispatchLayoutStep1方法主要是根據(jù)mState.mRunSimpleAnimationsmState.mRunPredictiveAnimations兩個(gè)值做出相應(yīng)邏輯處理,而processAdapterUpdatesAndSetAnimationFlags()方法中計(jì)算了這兩個(gè)值:

private void processAdapterUpdatesAndSetAnimationFlags() {

    ...
    
    mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator != null &&
            (mDataSetHasChangedAfterLayout || animationTypeSupported ||
                    mLayout.mRequestedSimpleAnimations) &&
            (!mDataSetHasChangedAfterLayout || mAdapter.hasStableIds());
    mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations &&
            animationTypeSupported && !mDataSetHasChangedAfterLayout &&
            predictiveItemAnimationsEnabled();
}

此方法中我們注意下mFirstLayoutComplete變量,mRunSimpleAnimations和mFirstLayoutComplete有關(guān),而mRunPredictiveAnimations 又和mRunSimpleAnimations有關(guān),第一次繪制流程還未完成,mFirstLayoutComplete 為false,因此mRunSimpleAnimations 和mRunPredictiveAnimations都為false,所以不會(huì)加載動(dòng)畫(huà),這也是很明顯的道理,布局還未加載完成,怎么會(huì)進(jìn)行加載動(dòng)畫(huà)呢。

dispatchLayoutStep1方法的最后一句是mState.mLayoutStep = State.STEP_LAYOUT,可見(jiàn)執(zhí)行完之后其改變了當(dāng)前繪制狀態(tài)。
dispatchLayoutStep1的其余具體邏輯和ItemAnimator有關(guān),后續(xù)分析ItemAnimator再詳細(xì)說(shuō)明。

dispatchLayoutStep2方法

onMeasure方法執(zhí)行完dispatchLayoutStep1后,接著執(zhí)行dispatchLayoutStep2方法,其中對(duì)RecyclerView的子View進(jìn)行了layout:

private void dispatchLayoutStep2() {

    ...
    
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout
    mState.mInPreLayout = false;
    mLayout.onLayoutChildren(mRecycler, mState);

    ...
    
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    
    ...
}

此方法我們重點(diǎn)關(guān)注mLayout.onLayoutChildren(mRecycler, mState)這句話,onLayoutChildren 這個(gè)函數(shù)由 LayoutManager的子類實(shí)現(xiàn),主要是決定了子View的布局方式,具體的相應(yīng)代碼邏輯可以查看Android官方LayoutManager之一LinearLayoutManager,后續(xù)我們?cè)僭敿?xì)說(shuō)明,此方法最后又將mState.mLayoutStep置為State.STEP_ANIMATIONS。

onMeasure情況3:LayoutManager沒(méi)有開(kāi)啟自動(dòng)測(cè)量

最后再來(lái)看看LayoutManager沒(méi)有開(kāi)啟自動(dòng)測(cè)量的情況:

if (mHasFixedSize) {
    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
    return;
}
// custom 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);
}

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

如果mHasFixedSize為truesetHasFixedSize方法可以設(shè)置此變量),就直接調(diào)用LayoutManager.onMeasure方法進(jìn)行測(cè)量;如果mHasFixedSize為false,則先判斷是否有數(shù)據(jù)更新(mAdapterUpdateDuringMeasure變量),有的話先處理數(shù)據(jù)更新,再調(diào)用LayoutManager.onMeasure方法進(jìn)行測(cè)量

繪制第二步:onLayout方法

測(cè)量過(guò)程之后便是layout過(guò)程,onLayout方法比較簡(jiǎn)單,只有下面幾行,最重要的邏輯都在dispatchLayout方法中了:

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    dispatchLayout();
    TraceCompat.endSection();
    mFirstLayoutComplete = true;
}

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()方法也簡(jiǎn)潔明了,首先分別判斷了mAdapter和mLayout,只要其中一個(gè)為null,則直接返回;這里需要注意到當(dāng)mLayout為null時(shí),即RecyclerView沒(méi)有設(shè)置LayoutManager時(shí),dispatchLayout方法直接返回了,因此不會(huì)處理layout過(guò)程,自然也解釋了為什么不設(shè)置LayoutManager,RecyclerView就不會(huì)加載數(shù)據(jù)。

dispatchLayout()方法中保證了RecyclerView必須經(jīng)歷三個(gè)過(guò)程,分別是dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3,前兩個(gè)方法我們?cè)趏nMeasure過(guò)程中已經(jīng)提到了,下面看下dispatchLayoutStep3源碼:

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

它重新將mState.mLayoutStep的狀態(tài)置為State.STEP_START,保證了第二次layout時(shí)仍會(huì)執(zhí)行dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3三個(gè)方法,剩下的工作主要是和Item動(dòng)畫(huà)相關(guān)的,和ItemAnimator有關(guān)。

如果在RecyclerView中如果開(kāi)啟了自動(dòng)測(cè)量,在measure階段就已經(jīng)將子View布局完成了,如果沒(méi)有開(kāi)啟自動(dòng)測(cè)量,那么會(huì)在layout階段再布局子View。

繪制第三步:draw方法

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.
    ......
}

draw大概可以分為三步

  1. 調(diào)用super.draw,將子View的繪制分發(fā)給View類;在View類draw方法中又會(huì)回調(diào)RecyclerView的onDraw方法:
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);
    }
}

此方法中又將分割線的繪制分發(fā)給ItemDecoration的onDraw方法

  1. 調(diào)用ItemDecoration的onDrawOver方法。通過(guò)這個(gè)方法,我們?cè)诿總€(gè)Item上自定義一些裝飾。
  2. 如果RecyclerView調(diào)用了setClipToPadding,會(huì)實(shí)現(xiàn)一種特殊的滑動(dòng)效果--每個(gè)ItemView可以滑動(dòng)到padding區(qū)域。

繪制流程補(bǔ)充舉例:LinearLayoutManager.onLayoutChildren方法

在講解ListView的繪制過(guò)程中,我們的重心就是layoutChildren方法,講解了怎么對(duì)子View布局,到現(xiàn)在為止我們還沒(méi)有進(jìn)入RecyclerView對(duì)子View布局的講解,前面描述dispatchLayoutStep2過(guò)程中,我們提到了onLayoutChildren 這個(gè)函數(shù)由LayoutManager的子類實(shí)現(xiàn),那么下面我們就以LayoutManager的子類LinearLayoutManager為例,介紹下onLayoutChildren的工作過(guò)程:

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
    
    ...
    
    // 第一步
    resolveShouldLayoutReverse();
    
    if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION ||
            mPendingSavedState != null) {
        mAnchorInfo.reset();
        mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
        // 計(jì)算錨點(diǎn)位置和坐標(biāo)
        updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
        mAnchorInfo.mValid = true;
    }
    
    ...
    
    // 第二步
    onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
    detachAndScrapAttachedViews(recycler);
    mLayoutState.mInfinite = resolveIsInfinite();
    mLayoutState.mIsPreLayout = state.isPreLayout();
    
    // 第三步
    if (mAnchorInfo.mLayoutFromEnd) {
        // fill towards start
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtra = extraForStart;
        fill(recycler, mLayoutState, state, false);
        
        ...
        
        // fill towards end
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtra = extraForEnd;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        
        ...
        
    } else {
        // fill towards end
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtra = extraForEnd;
        fill(recycler, mLayoutState, state, false);
        
        ...
        
        // fill towards start
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtra = extraForStart;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);
        
        ...
        
    }

    ...
    
}

子方法的開(kāi)頭注釋說(shuō)了整個(gè)大概的流程:

  • 1、確定錨點(diǎn)信息,找到一個(gè)錨點(diǎn)坐標(biāo)與錨點(diǎn)(如果是在線性布局中,相當(dāng)于找到當(dāng)前界面內(nèi)第一個(gè)View,與第一個(gè)view的坐標(biāo)點(diǎn)
  • 2、根據(jù)錨點(diǎn)信息,進(jìn)行填充
  • 3、填充完后,如果還有剩余的可填充大小,再填充一次

第一步:確定錨點(diǎn)信息

onLayoutChildren方法中大概做了三件事,第一步是確定錨點(diǎn)信息,首先執(zhí)行resolveShouldLayoutReverse()方法判斷是否需要倒著繪制

private void resolveShouldLayoutReverse() {
    if (mOrientation == VERTICAL || !isLayoutRTL()) {
        mShouldReverseLayout = mReverseLayout;
    } else {
        mShouldReverseLayout = !mReverseLayout;
    }
}

默認(rèn)情況下mReverseLayout為false,是不會(huì)倒著繪制的。手動(dòng)調(diào)用setReverseLayout()方法,可以改變mReverseLayout的值:

public void setReverseLayout(boolean reverseLayout) {
    assertNotInLayoutOrScroll(null);
    if (reverseLayout == mReverseLayout) {
        return;
    }
    mReverseLayout = reverseLayout;
    requestLayout();
}

接下來(lái)便是通過(guò)updateAnchorInfoForLayout()方法來(lái)計(jì)算錨點(diǎn)信息,這里對(duì)錨點(diǎn)做一些解釋,mAnchorInfo(AnchorInfo類對(duì)象)就是我們要的錨點(diǎn):

AnchorInfo(錨點(diǎn))

class AnchorInfo {
    int mPosition;
    int mCoordinate;
    boolean mLayoutFromEnd;
    boolean mValid;
    
    ...
    
}

AnchorInfo中有四個(gè)重要的成員變量,mPositionm和mCoordinate易懂;mValid的默認(rèn)值是false,一次測(cè)量之后設(shè)為true,onLayout完成后會(huì)回調(diào)執(zhí)行reset方法,又變?yōu)閒alse;mLayoutFromEnd變量在計(jì)算錨點(diǎn)過(guò)程中有如下賦值:

mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd

mShouldReverseLayout默認(rèn)是false,mStackFromEnd默認(rèn)是false,除非手動(dòng)調(diào)用setStackFromEnd()方法,兩個(gè)變量都是false,異或運(yùn)算之后還是false。

摘自RecyclerView源碼解析(一)——繪制流程

接下來(lái)又調(diào)用了updateAnchorInfoForLayout()方法,此方法用來(lái)更新錨點(diǎn)信息,一共有三種計(jì)算方法

private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
        AnchorInfo anchorInfo) {
    // 第一種計(jì)算方法
    if (updateAnchorFromPendingData(state, anchorInfo)) {
        return;
    }
    
    // 第二種計(jì)算方法
    if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
        return;
    }
    
    // 第三種計(jì)算方法
    anchorInfo.assignCoordinateFromPadding();
    anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
}
  1. 第一種計(jì)算方法如果存在未決的滾動(dòng)位置或保存的狀態(tài),從該數(shù)據(jù)更新錨點(diǎn)信息并返回true
  2. 第二種計(jì)算方法根據(jù)子View來(lái)更新錨點(diǎn)信息,如果一個(gè)子View有焦點(diǎn),則根據(jù)其來(lái)計(jì)算錨點(diǎn)信息;如有一個(gè)子View沒(méi)有錨點(diǎn),則根據(jù)布局方向選取第一個(gè)View或最后一個(gè)View
  3. 第三種計(jì)算方法前兩種都未采用,則采取默認(rèn)的第三種計(jì)算方式

第二步:回收子View

第二步是調(diào)用detachAndScrapAttachedViews()方法對(duì)所有的ItemView進(jìn)行回收,這部分的內(nèi)容屬于RecyclerView緩存機(jī)制的部分,后面解釋緩存的時(shí)候再說(shuō)。

第三步:子View填充(fill)

第三步是便是子View的內(nèi)容填充了首先是根據(jù)mAnchorInfo.mLayoutFromEnd來(lái)判斷是否逆向填充,無(wú)論是正向還是逆向,都調(diào)用了至少兩次fill()方法來(lái)進(jìn)行填充;如果是正向填充的話先向下填充,再向上填充;逆向的話和正向相反,兩次fill之后,如果還有剩余空間還會(huì)再調(diào)用一次fill進(jìn)行補(bǔ)充。我們來(lái)結(jié)合一張圖了解下錨點(diǎn)和fill之間的關(guān)系

fill

摘自RecyclerView全面的源碼解析

下面我們來(lái)看看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中真正填充子View的方法是layoutChunk(),再來(lái)看看layoutChunk()方法,源碼如下:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    // 第一步
    View view = layoutState.next(recycler);
    
    ...
    
    // 第二步
    if (layoutState.mScrapList == null) {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addView(view);
        } else {
            addView(view, 0);
        }
    } else {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addDisappearingView(view);
        } else {
            addDisappearingView(view, 0);
        }
    }
    
    // 第三步
    measureChildWithMargins(view, 0, 0);
    ...
    
    // 第四步
    layoutDecoratedWithMargins(view, left, top, right, bottom);

    ...
}

layoutChunk方法中的主要工作可以分為以下幾步:

  • 1.調(diào)用LayoutState的next方法獲得一個(gè)ItemView,next方法的參數(shù)是Recycler對(duì)象,Recycler正是RecyclerView的緩存核心實(shí)現(xiàn),可見(jiàn)RecyclerView中的緩存機(jī)制從此處開(kāi)始,后續(xù)分析緩存時(shí)再來(lái)具體查看
  • 2.如果RecyclerView是第一次布局children(layoutState.mScrapList == null),會(huì)調(diào)用addView()方法將View添加到RecyclerView中
  • 3.調(diào)用measureChildWithMargins方法,測(cè)量每個(gè)ItemView的寬高,這里考慮了margin屬性和ItemDecoration的offset
  • 4.調(diào)用了layoutDecoratedWithMargins對(duì)子View完成了布局

其中有很多細(xì)節(jié)并未展開(kāi)描述,后續(xù)分析緩存機(jī)制時(shí)再進(jìn)行講解。

總結(jié)

  1. RecyclerView的measure過(guò)程分為三種情況,每種情況都有執(zhí)行過(guò)程,一般情況下會(huì)走自動(dòng)測(cè)量流程
  2. 自動(dòng)測(cè)量根據(jù)mState.mLayoutStep狀態(tài)值,調(diào)用不同的dispatchLayoutStep方法
  3. layout過(guò)程也根據(jù)mState.mLayoutStep狀態(tài)來(lái)調(diào)用不同的dispatchLayoutStep方法
  4. draw過(guò)程大概可以分為三步
  5. 布局子View先獲取錨點(diǎn)信息,再根據(jù)錨點(diǎn)信息和布局方向進(jìn)行子View填充

摘自RecyclerView全面的源碼解析

至此基本完成了RecyclerView三大繪制流程,還有最重要的緩存機(jī)制沒(méi)有講解,見(jiàn)后續(xù)。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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