RecyclerView繪制過程分析

一 相關(guān)類的作用

RecyclerView具有很好的可擴(kuò)展性。我們可以自定義子view的排列,可以指定動(dòng)畫和分割線,另外RecyclerView還有一套自己的緩存機(jī)制。RecyclerView將不同的工作交給不同的類去處理。

LayoutoutManager

LayoutoutManager是RecyclerView中的一個(gè)抽象靜態(tài)內(nèi)部類。LayoutManager負(fù)責(zé)RecyclerView的布局,其中包含了Item View的獲取與回收。

LayoutManager中有幾個(gè)抽象方法非常重要:

  • onLayoutChildren(): 對(duì)RecyclerView進(jìn)行布局的入口方法。

  • fill(): 負(fù)責(zé)填充RecyclerView。

  • scrollVerticallyBy():根據(jù)手指的移動(dòng)滑動(dòng)一定距離,并調(diào)用fill()填充。

  • canScrollVertically()或canScrollHorizontally(): 判斷是否支持縱向滑動(dòng)或橫向滑動(dòng)。

ItemDecoration

ItemDecoration 是為了顯示每個(gè) item 之間分隔樣式的。它的本質(zhì)實(shí)際上就是一個(gè) Drawable。當(dāng) RecyclerView 執(zhí)行到 onDraw() 方法的時(shí)候,就會(huì)調(diào)用到他的 onDraw(),這時(shí),如果你重寫了這個(gè)方法,就相當(dāng)于是直接在 RecyclerView 上畫了一個(gè) Drawable 表現(xiàn)的東西。 而最后,在他的內(nèi)部還有一個(gè)叫g(shù)etItemOffsets()的方法,從字面就可以理解,他是用來偏移每個(gè) item 視圖的。當(dāng)我們?cè)诿總€(gè) item 視圖之間強(qiáng)行插入繪畫了一段 Drawable,那么如果再照著原本的邏輯去繪 item 視圖,就會(huì)覆蓋掉 Decoration 了,所以需要getItemOffsets()這個(gè)方法,讓每個(gè) item 往后面偏移一點(diǎn),不要覆蓋到之前畫上的分隔樣式了。

ItemAnimator

每一個(gè) item 在特定情況下都會(huì)執(zhí)行的動(dòng)畫。說是特定情況,其實(shí)就是在視圖發(fā)生改變,我們手動(dòng)調(diào)用notifyxxxx()的時(shí)候。通常這個(gè)時(shí)候我們會(huì)要傳一個(gè)下標(biāo),那么從這個(gè)標(biāo)記開始一直到結(jié)束,所有 item 視圖都會(huì)被執(zhí)行一次這個(gè)動(dòng)畫。

Adapter

首先是適配器,適配器的作用都是類似的,用于提供每個(gè) item 視圖,并返回給 RecyclerView 作為其子布局添加到內(nèi)部。

但是,與 ListView 不同的是,ListView 的適配器是直接返回一個(gè) View,將這個(gè) View 加入到 ListView 內(nèi)部。而 RecyclerView 是返回一個(gè) ViewHolder 并且不是直接將這個(gè) holder 加入到視圖內(nèi)部,而是加入到一個(gè)緩存區(qū)域,在視圖需要的時(shí)候去緩存區(qū)域找到 holder 再間接的找到 holder 包裹的 View。

ViewHolder

每個(gè) ViewHolder 的內(nèi)部是一個(gè) View,并且 ViewHolder 必須繼承自RecyclerView.ViewHolder類。 這主要是因?yàn)?RecyclerView 內(nèi)部的緩存結(jié)構(gòu)并不是像 ListView 那樣去緩存一個(gè) View,而是直接緩存一個(gè) ViewHolder ,在 ViewHolder 的內(nèi)部又持有了一個(gè) View。既然是緩存一個(gè) ViewHolder,那么當(dāng)然就必須所有的 ViewHolder 都繼承同一個(gè)類才能做到了。

二 繪制流程

RecyclerView的聲明如下:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {} 

可見,RecyclerView本質(zhì)還是一個(gè)ViewGroup,那么其繪制流程肯定遵循view的繪制流程:onMeasure - onLayout - onDraw。那么就去看看這幾個(gè)方法里做了些什么。

onMeasure

首先要了解下mLayout是什么,它是一個(gè)LayoutManager,RecyclerView中的一個(gè)成員變量,通過setLayoutManager方法賦值:

public void setLayoutManager(LayoutManager layout) {
    if (layout == mLayout) {
        return;
    }
    .......
    mLayout = layout;
    if (layout != null) {
        if (layout.mRecyclerView != null) {
            throw new IllegalArgumentException("LayoutManager " + layout
                    + " is already attached to a RecyclerView:"
                    + layout.mRecyclerView.exceptionLabel());
        }
        mLayout.setRecyclerView(this);
        if (mIsAttached) {
            mLayout.dispatchAttachedToWindow(this);
        }
    }
    mRecycler.updateViewCacheSize();
    requestLayout();
}

可以看到,我們?cè)谑褂肦ecyclerView中,setLayoutManager就是把我們自定義的LayoutManager傳遞給了mLayout。

回到正題,下面是RecyclerView中onMeasure的源碼:

protected void onMeasure(int widthSpec, int heightSpec) {
    /** 1.判斷mLayout是否為null,為null執(zhí)行defaultOnMeasure,return **/
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    /** 2.isAutoMeasureEnabled()一般都是true **/
    if (mLayout.isAutoMeasureEnabled()) {
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        /** 3.調(diào)用了LayoutManager的onMeasure **/
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        /** 4.當(dāng)RecyclerView的寬高都是EXACTLY時(shí),skipMeasure為true,那么接下來直接return **/
        final boolean measureSpecModeIsExactly =
                widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
        if (measureSpecModeIsExactly || mAdapter == null) {
            return;
        }
        /** 5.mState.mLayoutStep為STEP_START,這里會(huì)執(zhí)行dispatchLayoutStep1(); **/
        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;
        /** 6.執(zhí)行dispatchLayoutStep2();這個(gè)方法很重要,下面繼續(xù)分析 **/
        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);
        }
    } else {
        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
    }
}

代碼里注釋了重要的步驟,應(yīng)該都挺好理解的。下面詳細(xì)分析下。

1.mLayout為null時(shí),defaultOnMeasure(widthSpec, heightSpec);做了什么?

/** 
* An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios 
* where this RecyclerView is otherwise lacking better information. 
*/ 
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);
}

mLayout為null說明我們沒有指定RecyclerView的子view如何布局和繪制,那么這里就是很常規(guī)的根據(jù)SpecMode設(shè)置RecyclerView的寬高,其它什么都沒干,這也是我們不調(diào)用setLayoutManager方法導(dǎo)致RecyclerView空白的效果。

2.isAutoMeasureEnabled()

/**Returns whether the measuring pass of layout should use the AutoMeasure mechanism of
{@link RecyclerView} or if it should be done by the LayoutManager's implementation of
{@link LayoutManager#onMeasure(Recycler, State, int, int)}.
*/
public boolean isAutoMeasureEnabled() {
    return mAutoMeasure;
}

也就是說,返回true,那么執(zhí)行RecyclerView的autoMeasure機(jī)制,否則就執(zhí)行LayoutManager的實(shí)現(xiàn)的onMeasure方法。LinearLayoutManager的這個(gè)參數(shù)為true。

3.mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);的作用

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

驗(yàn)證了第二步,這里執(zhí)行mRecyclerView的defaultOnMeasure(widthSpec, heightSpec);這個(gè)方法最后會(huì)執(zhí)行setMeasuredDimension(width, height);

4.measureSpecModeIsExactly 為true時(shí),為什么直接return了?

final boolean measureSpecModeIsExactly =
                widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;

當(dāng)widthMode和heightMode為EXACTLY時(shí),measureSpecModeIsExactly 為true,這個(gè)時(shí)候,onMeasure可以直接返回了。為什么呢?因?yàn)楫?dāng)widthMode和heightMode都為EXACTLY時(shí),說明RecyclerView的寬高都是固定的,不用根據(jù)子view的寬高來確定自身的寬高,所以執(zhí)行完第3步,其實(shí)RecyclerView就已經(jīng)確定大小了。

5.dispatchLayoutStep1()和dispatchLayoutStep2()

其實(shí)還有個(gè)dispatchLayoutStep3(),這三個(gè)方法才是RecyclerView的繪制流程。下面是三個(gè)方法的具體作用:

  • dispatchLayoutStep1: Adapter的更新; 決定該啟動(dòng)哪種動(dòng)畫; 保存當(dāng)前View的信息(getLeft(), getRight(), getTop(), getBottom()等); 如果有必要,先跑一次布局并將信息保存下來。

  • dispatchLayoutStep2: 真正對(duì)子View做布局的地方。

  • dispatchLayoutStep3: 為動(dòng)畫保存View的相關(guān)信息; 觸發(fā)動(dòng)畫; 相應(yīng)的清理工作。

這里還牽扯到了mState.mLayoutStep,這個(gè)變量在每個(gè)step值都是不一樣的,具體表現(xiàn)如下:

  • 初始化為STEP_START

  • 執(zhí)行完dispatchLayoutStep1后,mState.mLayoutStep = State.STEP_LAYOUT;

  • 執(zhí)行完dispatchLayoutStep2后,mState.mLayoutStep = State.STEP_ANIMATIONS;

  • 執(zhí)行完dispatchLayoutStep3后,mState.mLayoutStep = State.STEP_LAYOUT;

所以我們可以根據(jù)mState.mLayoutStep的值來確定我們處于哪個(gè)狀態(tài)。

onLayout

接下來分析onLayout方法。

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

這里會(huì)根據(jù)mState.mLayoutStep判斷是否要執(zhí)行dispatchLayoutStep1();和dispatchLayoutStep2();方法,最后調(diào)用dispatchLayoutStep3();。

在onMeasure和onLayout過程中,RecyclerView將大部分的邏輯都交給了dispatchLayoutStep1()、dispatchLayoutStep2()和dispatchLayoutStep3()處理,那么就針對(duì)這三個(gè)方法分析一下。

dispatchLayoutStep1()
/**
* The first step of a layout where we;
* - process adapter updates  (處理Adapter更新)
* - decide which animation should run   (決定執(zhí)行哪個(gè)動(dòng)畫)
* - save information about current views    (保存當(dāng)前view的一些信息)
* - If necessary, run predictive layout and save its information
*/
private void dispatchLayoutStep1() {
    .......
    /**處理Adapter和動(dòng)畫相關(guān)**/
    processAdapterUpdatesAndSetAnimationFlags();
    .......
    if (mState.mRunSimpleAnimations) {
        // Step 0: Find out where all non-removed items are, pre-layout
        int count = mChildHelper.getChildCount();
        for (int i = 0; i < count; ++i) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
            if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                continue;
            }
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPreLayoutInformation(mState, holder,
                            ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                            holder.getUnmodifiedPayloads());
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                    && !holder.shouldIgnore() && !holder.isInvalid()) {
                long key = getChangedHolderKey(holder);
                mViewInfoStore.addToOldChangeHolders(key, holder);
            }
        }
    }
    if (mState.mRunPredictiveAnimations) {
        saveOldPositions();
        final boolean didStructureChange = mState.mStructureChanged;
        mState.mStructureChanged = false;
        /**對(duì)子view進(jìn)行布局**/
        // temporarily disable flag because we are asking for previous layout
        mLayout.onLayoutChildren(mRecycler, mState);
        mState.mStructureChanged = didStructureChange;
        for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
            final View child = mChildHelper.getChildAt(i);
            final ViewHolder viewHolder = getChildViewHolderInt(child);
            if (viewHolder.shouldIgnore()) {
                continue;
            }
            if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
                boolean wasHidden = viewHolder
                        .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                if (!wasHidden) {
                    flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                }
                final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                        mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                if (wasHidden) {
                    recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                } else {
                    mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                }
            }
        }
        // we don't process disappearing list because they may re-appear in post layout pass.
        clearOldPositions();
    } else {
        clearOldPositions();
    }
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    /**這里改變了mState.mLayoutStep的值**/
    mState.mLayoutStep = State.STEP_LAYOUT;
}

當(dāng)我們的RecyclerView有動(dòng)畫時(shí),才可能執(zhí)行兩個(gè)if語句里的代碼。如果mState.mRunPredictiveAnimations為true,會(huì)先調(diào)用一次mLayout.onLayoutChildren(mRecycler, mState);。這個(gè)方法等下講解。

dispatchLayoutStep2()
private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
    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的值**/
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
}

dispatchLayoutStep2()貌似很簡(jiǎn)單,沒做什么事情。其實(shí)不然,它調(diào)用了mLayout.onLayoutChildren(mRecycler, mState);方法,這個(gè)方法才是重頭戲。RecyclerView將子View的繪制交給了LayoutManager去做,其實(shí)就是在onLayoutChildren這個(gè)方法里處理的。

onLayoutChildren方法在子類實(shí)現(xiàn)。那么來看看LinearLayoutManager里的onLayoutChildren方法。

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    ......
    //判斷繪制方向,給mShouldReverseLayout賦值,默認(rèn)是正向繪制,則mShouldReverseLayout是false
    resolveShouldLayoutReverse();
    final View focused = getFocusedChild();
    if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
            || mPendingSavedState != null) {
        mAnchorInfo.reset();
        mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
        // 計(jì)算錨點(diǎn)的位置和偏移量
        updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
        mAnchorInfo.mValid = true;
    } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                    >= mOrientationHelper.getEndAfterPadding()
            || mOrientationHelper.getDecoratedEnd(focused)
            <= mOrientationHelper.getStartAfterPadding())) {
        mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
    }
    ......
    if (mAnchorInfo.mLayoutFromEnd) {
        ......
    } 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;
        }
    }
    ......
}

這個(gè)方法實(shí)在是復(fù)雜,即使刪掉了部分代碼還是有點(diǎn)長,但是我們只關(guān)心子view是如何繪制的。

這里有個(gè)錨點(diǎn)的概念。理解為要開始繪制的位置就可以了。

錨點(diǎn)和填充的關(guān)系.png

一開始我對(duì)這張圖有點(diǎn)懵逼。其實(shí)抓住那個(gè)小紅點(diǎn)就可以了,小紅點(diǎn)就是所謂的錨點(diǎn),也就是繪制的開始位置。第一種情況中,小紅點(diǎn)在最底部,那么就從n-1的位置開始繪制子view。第二種情況中,小紅點(diǎn)在頂部,從0開始繪制子view。

這張圖就描述了onLayoutChildren里的主要工作。

updateAnchorInfoForLayout和updateLayoutStateToFillEnd等幾個(gè)類似的方法都是對(duì)錨點(diǎn)位置的計(jì)算。那么哪個(gè)方法真正執(zhí)行了繪制工作呢?答案是fill()方法。

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    ......
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    /**判斷是否還有空間,循環(huán)執(zhí)行l(wèi)ayoutChunk方法**/
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        ......
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        ......
        if (layoutChunkResult.mFinished) {
            break;
        }
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
 
        if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                || !state.isPreLayout()) {
            layoutState.mAvailable -= layoutChunkResult.mConsumed;
            remainingSpace -= layoutChunkResult.mConsumed;
        }
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
        if (stopOnFocusable && layoutChunkResult.mFocusable) {
            break;
        }
    }
    return start - layoutState.mAvailable;
}

繼續(xù)找layoutChunk()方法:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    /**尋找要繪制的view,這個(gè)next方法可是相當(dāng)重要的,里面包含RecyclerView的緩存邏輯,以后再分析吧**/
    View view = layoutState.next(recycler);
    if (view == null) {
        return;
    }
    LayoutParams params = (LayoutParams) view.getLayoutParams();
    /**將子view add到RecyclerView中**/
    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);
        }
    }

    /**對(duì)子veiw進(jìn)行一次測(cè)量**/
    measureChildWithMargins(view, 0, 0);

    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);

    /**這四個(gè)變量是子view的邊距**/
    int left, top, right, bottom;
    if (mOrientation == VERTICAL) {
        if (isLayoutRTL()) {
            right = getWidth() - getPaddingRight();
            left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
        } else {
            left = getPaddingLeft();
            right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
        }
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            bottom = layoutState.mOffset;
            top = layoutState.mOffset - result.mConsumed;
        } else {
            top = layoutState.mOffset;
            bottom = layoutState.mOffset + result.mConsumed;
        }
    } else {
        top = getPaddingTop();
        bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            right = layoutState.mOffset;
            left = layoutState.mOffset - result.mConsumed;
        } else {
            left = layoutState.mOffset;
            right = layoutState.mOffset + result.mConsumed;
        }
    }
    /**對(duì)子view進(jìn)行l(wèi)ayout**/
    layoutDecoratedWithMargins(view, left, top, right, bottom);
   
    // Consume the available space if the view is not removed OR changed
    if (params.isItemRemoved() || params.isItemChanged()) {
        result.mIgnoreConsumed = true;
    }
    result.mFocusable = view.hasFocusable();
}

可見,我們終于找到了對(duì)子view進(jìn)行measure和layout的地方。

這里總結(jié)下dispatchLayoutStep2()方法中做的事:

  1. 計(jì)算錨點(diǎn),以錨點(diǎn)開始填充RecyclerView(其實(shí)就是執(zhí)行fill方法)。

  2. 執(zhí)行fill方法,判斷RecyclerView是否還有空間,如果有,執(zhí)行l(wèi)ayoutChunk方法,直至填充滿。

  3. layoutChunk方法中,尋找到當(dāng)前要添加的子view,add到RecyclerView中。

  4. 對(duì)子view進(jìn)行measure和layout。

至此,dispatchLayoutStep2()就結(jié)束了。

dispatchLayoutStep3()

onLayout中會(huì)調(diào)用dispatchLayoutStep3()方法。

private void dispatchLayoutStep3() {
    ......
    /**重置了mState.mLayoutStep**/
    mState.mLayoutStep = State.STEP_START;
    /**處理動(dòng)畫**/
    if (mState.mRunSimpleAnimations) {
        ......
    }
    mLayout.removeAndRecycleScrapInt(mRecycler);
    mState.mPreviousLayoutItemCount = mState.mItemCount;
    mDataSetHasChangedAfterLayout = false;
    mDispatchItemsChangedEvent = false;
    mState.mRunSimpleAnimations = false;
    mState.mRunPredictiveAnimations = false;
    mLayout.mRequestedSimpleAnimations = false;
    if (mRecycler.mChangedScrap != null) {
        mRecycler.mChangedScrap.clear();
    }
    if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
        mLayout.mPrefetchMaxCountObserved = 0;
        mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
        mRecycler.updateViewCacheSize();
    }
    /**表明layout過程結(jié)束**/
    mLayout.onLayoutCompleted(mState);
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    mViewInfoStore.clear();
    if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
        dispatchOnScrolled(0, 0);
    }
    recoverFocusFromState();
    resetFocusInfo();
}

其實(shí)dispatchLayoutStep3()就是做了一些收尾工作,將一些變量重置,處理下動(dòng)畫。

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

看到這么簡(jiǎn)單的方法真是開心。這里的邏輯確實(shí)很簡(jiǎn)單,還記得ItemDecoration嗎?我們一般用這個(gè)來實(shí)現(xiàn)分割線效果,如果我們執(zhí)行了addItemDecoration,這里RecyclerView的onDraw方法就幫我們把這些分割線繪制出來。

三、總結(jié)

1.RecyclerView是將繪制流程交給LayoutManager處理,如果沒有設(shè)置不會(huì)繪制子View。

2.繪制是先確定錨點(diǎn),以錨點(diǎn)開始,向上繪制,向下繪制,fill()至少會(huì)執(zhí)行兩次,如果繪制完還有剩余空間,則會(huì)再執(zhí)行一次fill()方法。

3.fill方法中會(huì)根據(jù)剩余空間循環(huán)執(zhí)行l(wèi)ayoutChunk方法。

4.layoutChunk方法add了子view,并對(duì)子view進(jìn)行了measure和layout。

?著作權(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)容