RecyclerView 原始碼深入解析——繪製流程、快取機制、動畫等

zouzhiheng發表於2018-11-10

前言

本文打算對 RecyclerView 做一個詳細完整的、重點突出的分析與總結,因為 RecycelrView 原始碼很長(光 RecyclerView 檔案本身就有 13000+ 行),因此文章也會很長,但一通分析下來後會發現,RecyclerView 雖然是 ListView 的加強版,除了在使用方法上類似之外,關鍵原始碼上也是非常類似的。

RecyclerView 的使用可以參考大神的文章:

Android RecyclerView 使用完全解析 體驗藝術般的控制元件

RecyclerView 和 ListView 使用對比分析

本文采用“自頂向下”的原始碼分析法——即把相關的程式碼按照呼叫關係自頂向下排列,同一個類的方法放在一起,關鍵的程式碼使用註釋標註。廢話不多說,下面從 RecyclerView 的繪製流程開始分析。

繪製流程

先從 onMeasure 方法看起,關鍵的地方都用註釋標出來了:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    Adapter mAdapter;
    @VisibleForTesting LayoutManager mLayout;

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        if (mLayout.isAutoMeasureEnabled()) { // 看這裡就行了
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);

            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

			// 關鍵步驟 1
            // 預設為 start
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }
            
            // 關鍵步驟 2
            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()) {
                ...
                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) {
                ...
            } else if (mState.mRunPredictiveAnimations) {
                setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
                return;
            }

            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            ...
        }
    }
    
    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);
    }
    
    public abstract static class LayoutManager {
    
        public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
            mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
        }
        
    }
    
}
複製程式碼

mLayout.isAutoMeasureEnabled() 的返回值預設為 false,但 LinearLayoutManager 和 StaggeredGridLayoutManager 重寫了這個方法,且預設為 true:

public class LinearLayoutManager extends RecyclerView.LayoutManager implements
        ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {

    @Override
    public boolean isAutoMeasureEnabled() {
        return true;
    }
    
}
複製程式碼
public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager implements
        RecyclerView.SmoothScroller.ScrollVectorProvider {

    public static final int GAP_HANDLING_NONE = 0;
    public static final int GAP_HANDLING_LAZY = 1;
    public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;

    private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
    
    public void setGapStrategy(int gapStrategy) {
        ...
        mGapStrategy = gapStrategy;
        requestLayout();
    }

    @Override
    public boolean isAutoMeasureEnabled() {
        return mGapStrategy != GAP_HANDLING_NONE;
    }
    
}
複製程式碼

而 GridLayoutManager 繼承自 LinearLayoutManager,即 Android 提供的 LayoutManager 的三個子類,都會走 if 分支裡的程式碼,裡面最重要的兩個呼叫是 dispatchLayoutStep1 和 dispatchLayoutStep2,在開始分析這兩個方法之前,先看一下 onLayout 方法:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        dispatchLayout();
        mFirstLayoutComplete = true;
    }
    
    /**
     * Wrapper around layoutChildren() that handles animating changes caused by layout.
     */
    void dispatchLayout() {
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) { 
            // onMeasure 已經執行了 step1 和 step2,正常情況下不會走這個分支
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            // adpter 更新了,或者 width 或 height 改變了,重新走一遍 step2
            // First 2 steps are done in onMeasure but looks like we have to run again due to
            // changed size.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // 設定 MeasureSpec 為 EXACTLY
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        // 關鍵步驟 3
        dispatchLayoutStep3();
    }

    public abstract static class LayoutManager {
    
        void setExactMeasureSpecsFrom(RecyclerView recyclerView) {
            setMeasureSpecs(
                    MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY)
            );
        }
    }
    
}
複製程式碼

可以發現,RecyclerView 的 layout 流程分為三步,關鍵呼叫分別為 dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3,其中 onMeasure 方法執行前兩部,onLayout 方法執行最後一步,下面開始分析這三個方法。

dispatchLayoutStep

dispatchLayoutStep1

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
    
    /**
     * The first step of a layout where we;
     * - process adapter updates
     * - decide which animation should run
     * - save information about current views
     * - If necessary, run predictive layout and save its information
     */
    private void dispatchLayoutStep1() {
        ...
        processAdapterUpdatesAndSetAnimationFlags();

        if (mState.mRunSimpleAnimations) {
            // Step 0: 找出所有未刪除的 item 的位置, 準備執行預佈局
            int count = mChildHelper.getChildCount();
            for (int i = 0; i < count; ++i) {
                final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(...);
                mViewInfoStore.addToPreLayout(holder, animationInfo);
                ...
            }
        }
        
        if (mState.mRunPredictiveAnimations) {
            // Step 1: 執行預佈局

            // Save old positions so that LayoutManager can run its mapping logic.
            saveOldPositions();
            final boolean didStructureChange = mState.mStructureChanged;
            mState.mStructureChanged = false;
            // 藉助 LayoutManager 完成 layout 流程
            // 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 (!mViewInfoStore.isInPreLayout(viewHolder)) {
                    ...
                    final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(...);
                    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();
        }
        
        mState.mLayoutStep = State.STEP_LAYOUT;
    }
    
    private void processAdapterUpdatesAndSetAnimationFlags() {
        // 預設為 false,第一次 measure 時不會執行
        if (mDataSetHasChangedAfterLayout) {
            mAdapterHelper.reset();
            if (mDispatchItemsChangedEvent) {
                mLayout.onItemsChanged(this);
            }
        }
        
        // 兩個分支裡的程式碼最後呼叫的都是 AdapterHelper 內部的介面 Callback 的方法
        // Callback 的實現可以檢視 RecyclerView 的 initAdapterManager 方法
        // 最後可能會呼叫 requestLayout 走一遍繪製流程以實現動畫的效果
        if (predictiveItemAnimationsEnabled()) {
            mAdapterHelper.preProcess();
        } else {
            mAdapterHelper.consumeUpdatesInOnePass();
        }
        
        mState.mRunSimpleAnimations = mFirstLayoutComplete && ...;
        mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
                && animationTypeSupported
                && !mDataSetHasChangedAfterLayout
                && predictiveItemAnimationsEnabled();
    }
    
    static ViewHolder getChildViewHolderInt(View child) {
        if (child == null) {
            return null;
        }
        return ((LayoutParams) child.getLayoutParams()).mViewHolder;
    }
    
}
複製程式碼

根據 dispatchLayoutStep1 的註釋及程式碼,可以得知這個方法主要負責:

  1. 執行 adapter 更新,最終可能會呼叫 requestLayout
  2. 決定哪些動畫應該被執行(暫時不會被執行)
  3. 儲存子 View 的相關資訊
  4. 如果有必要,執行預佈局

而如果是第一次執行 measure 流程,adapter 正常來說是沒有可以更新的元素的,即此時 dispatchLayoutStep1 的作用主要是計算並儲存子 View 和動畫的相關資訊。

dispatchLayoutStep2

預佈局是否執行涉及的變數比較多,先忽略。現在繼續看 dispatchLayoutStep2:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    /**
     * The second layout step where we do the actual layout of the views for the final state.
     * This step might be run multiple times if necessary (e.g. measure).
     */
    private void dispatchLayoutStep2() {
        ...
        // 藉助 LayoutManager 完成實際上的佈局
        // Step 2: Run layout
        mState.mInPreLayout = false;
        mLayout.onLayoutChildren(mRecycler, mState);
        mState.mStructureChanged = false;
        ...
    }
    
}
複製程式碼

可以看到,相比 dispatchLayoutStep1,dispatchLayoutStep2 簡單得多,作用是執行真正的佈局,呼叫的方法是 mLayout.onLayoutChildren,這個方法在 LayoutManager 中是一個空實現,它的三個子類 LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager 都有自己的實現。下面以 LinearLayoutManager 為例(關鍵部分,比如快取機制,三個 LayoutManager 都是一樣的):

public class LinearLayoutManager extends RecyclerView.LayoutManager implements
        ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {

    @Override
    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
        
        if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
            if (state.getItemCount() == 0) {
                removeAndRecycleAllViews(recycler);
                return;
            }
        }
        
        ...

		// 尋找子 View 的錨點座標及位置
        final View focused = getFocusedChild();
        if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION || mPendingSavedState != null) {
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
        } else if (...) {
            mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
        }

        if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION
                && mPendingScrollPositionOffset != INVALID_OFFSET) {
            // if the child is visible and we are going to move it around, we should layout
            // extra items in the opposite direction to make sure new items animate nicely
            // instead of just fading in
            ...
        }

        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
        // 把子 View detach 掉並快取到 Recycler 裡面
        detachAndScrapAttachedViews(recycler);
        mLayoutState.mIsPreLayout = state.isPreLayout();
        if (mAnchorInfo.mLayoutFromEnd) {
            // fill towards start
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            // 填充子 View
            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
            ...
            
            // fill towards start
            ...
        }

        ...
        layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
        if (!state.isPreLayout()) {
            mOrientationHelper.onLayoutComplete();
        } else {
            mAnchorInfo.reset();
        }
        ...
    }
    
}
複製程式碼

結合註釋及程式碼,可以知道 onLayoutChildren 的作用是:尋找到子 View 的錨點座標及錨點位置後載入子 View。錨點座標及位置的計算方法忽略,下面重點關注 detachAndScrapAttachedViews 和 fill 這兩個方法。

detachAndScrapAttachedViews

這個方法在第一次 measure 的時候不會產生效果,因為此時 RecyclerView 還沒有子 View。而在第二第三次 layout 時,它會把子 View 從 RecyclerView 中 remove 或 detach ,並快取子 View,以便之後重新 add 回來或 attach 回來,避免重複載入相同的子 View——這點是 ListView 幾乎是一致的:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    public abstract static class LayoutManager {
    
        public void detachAndScrapAttachedViews(Recycler recycler) {
            final int childCount = getChildCount();
            for (int i = childCount - 1; i >= 0; i--) {
                final View v = getChildAt(i);
                scrapOrRecycleView(recycler, i, v);
            }
        }
        
        private void scrapOrRecycleView(Recycler recycler, int index, View view) {
            final ViewHolder viewHolder = getChildViewHolderInt(view);
            if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                    && !mRecyclerView.mAdapter.hasStableIds()) { 
                // 移除子 View 並使用 mCacheVies 或 RecycledViewPool 快取
                removeViewAt(index);
                recycler.recycleViewHolderInternal(viewHolder);
            } else {
                //  detach 子 View 並使用 mChangedScarp 或 mAttachedScarp 快取
                detachViewAt(index);
                recycler.scrapView(view);
                mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
            }
        }
        
        // add / remove 和 attach/ detach 都是通過 mChildHelper 完成的
        // 之後會呼叫 ChildHelper 內部的介面 Callback 的方法,Callback 的實現可以檢視 RecyclerView 的 initChildrenHelper
        // 最終還是通過 ViewGroup 的 add / remove 和 attach / detach 實現的
        public void removeViewAt(int index) {
            final View child = getChildAt(index);
            if (child != null) {
                mChildHelper.removeViewAt(index);
            }
        }
        
        public void detachViewAt(int index) {
            detachViewInternal(index, getChildAt(index));
        }

        private void detachViewInternal(int index, View view) {
            mChildHelper.detachViewFromParent(index);
        }
        
    }
    
    /**
     * A Recycler is responsible for managing scrapped or detached item views for reuse.
     *
     * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
     * that has been marked for removal or reuse.</p>
     *
     * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for
     * an adapter's data set representing the data at a given position or item ID.
     * If the view to be reused is considered "dirty" the adapter will be asked to rebind it.
     * If not, the view can be quickly reused by the LayoutManager with no further work.
     * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout}
     * may be repositioned by a LayoutManager without remeasurement.</p>
     */
    public final class Recycler {
    
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        ArrayList<ViewHolder> mChangedScrap = null;

        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

        private final List<ViewHolder>
                mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
                
        static final int DEFAULT_CACHE_SIZE = 2;

        private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
        // mCachedVies 最多放 2 個,這個容量限制會在 mCachedView 呼叫 add 方法時起作用
        int mViewCacheMax = DEFAULT_CACHE_SIZE;

        RecycledViewPool mRecyclerPool;
        
        private ViewCacheExtension mViewCacheExtension;

        void recycleViewHolderInternal(ViewHolder holder) {
            ...
            
            boolean cached = false;
            boolean recycled = false;
            if (forceRecycle || holder.isRecyclable()) {
                // 如果符合條件,就放到 mCachedViews 裡面
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                    // 超過快取容量限制,把最老的一個 View 放進 RecycledViewPool 裡面
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }

                    ...
                    //  使用 mCachedViews 快取子 View
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }

                // 否則放到 RecycledViewPool 裡面
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } else {
                ...
            }
            // even if the holder is not removed, we still call this method so that it is removed
            // from view holder lists.
            mViewInfoStore.removeViewHolder(holder);
        }
        
        // 把一個 View 從 mCachedViews 裡刪除,並 put 進 RecycledViewPool 裡面
        void recycleCachedViewAt(int cachedViewIndex) {
            ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
            addViewHolderToRecycledViewPool(viewHolder, true);
            mCachedViews.remove(cachedViewIndex);
        }
        
        void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
            ...
            getRecycledViewPool().putRecycledView(holder);
        }
    
        /**
         * Mark an attached view as scrap.
         */
        void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                    || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                mAttachedScrap.add(holder);
            } else {
                mChangedScrap.add(holder);
            }
        }
        
    }
    
    /**
     * RecycledViewPool lets you share Views between multiple RecyclerViews.
     */
    public static class RecycledViewPool {
        private static final int DEFAULT_MAX_SCRAP = 5;
        
        static class ScrapData {
            // mScrapHeap 最多放 5 個
            final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
            int mMaxScrap = DEFAULT_MAX_SCRAP;
            long mCreateRunningAverageNs = 0;
            long mBindRunningAverageNs = 0;
        }
        // 每種型別對應一個 ScrapData,即每種型別最多快取 5 個 View
        SparseArray<ScrapData> mScrap = new SparseArray<>();

        private int mAttachCount = 0;
        
        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            scrapHeap.add(scrap);
        }

        private ScrapData getScrapDataForType(int viewType) {
            ScrapData scrapData = mScrap.get(viewType);
            if (scrapData == null) {
                scrapData = new ScrapData();
                mScrap.put(viewType, scrapData);
            }
            return scrapData;
        }
        
    }
    
}
複製程式碼

可以看到,detachAndScrapAttachedViews 這個不起眼的方法已經觸及 RecyclerView 的核心了,它的作用有可以總結為一句話:remove / detach 所有子 View,並快取它們。其中,如果根據 ViewHolder 狀態得知子 View 可以被移除,則移除子 View,並快取到 Recycler 的 mCachedViews 陣列裡面,mCachedViews 預設只快取 2 個,如果 mCachedViews 容量超出限制,則把 mCachedViews 陣列裡最老的一個 item 移除,放到 RecycledViewPool 的 mScrapHeap 陣列裡。如果子 View 無法被移除,那麼 detach 掉,並判斷子 View 是否發生了改變,改變了的子 View 快取到 Recycler 的 mChangedScrap 陣列,否則快取到 Recycler 的 mAttachedScrap 陣列。

Recycler 和 RecycledViewPool 的區別是,一個 Recycler 對應一個 RecyclerView;一個 RecycledViewPool 可以對應多個 RecyclerView。即 RecycledViewPool 裡面的 View 可以提供給多個 RecyclerView 重用。

Scrap View 代表移除了的可以重用的檢視。如果即將要重用的 View 標記為 dirty,則需要 Adapter 重新繫結,否則 LayoutManager 可以直接使用。

detachAndScrapAttachedViews 就分析到這裡,接下來繼續分析 onLayoutChildren。 onLayoutChildren 方法中下一個需要關注的呼叫是 fill。

fill

fill 方法特別關鍵——如果說 detachAndScrapAttachedViews 觸及了 RecyclerView 的核心,那麼 fill 方法就是真正的核心。它是從 Adapter 中載入子 View、從回收池中複用 View、並顯示到 RecyclerView 上的關鍵呼叫,原始碼作者稱它為 magic method:

public class LinearLayoutManager extends RecyclerView.LayoutManager implements
        ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
        
    /**
     * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
     * independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager}
     * and with little change, can be made publicly available as a helper class.
     */
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        ...
        
        // 這個 while 迴圈是最關鍵的部分,它表明了 RecyclerView 和 ListView 一樣,只會載入一個手機螢幕能顯示的子 View
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            
            // 載入子 View
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            
            /**
             * Consume the available space if:
             * * layoutChunk did not request to be ignored
             * * OR we are laying out scrap children
             * * OR we are not doing pre-layout
             */
            if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                    || !state.isPreLayout()) {
                layoutState.mAvailable -= layoutChunkResult.mConsumed;
                // we keep a separate remaining space because mAvailable is important for recycling
                remainingSpace -= layoutChunkResult.mConsumed;
            }

            // 如果當前正在滾動螢幕
            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                // 把移出螢幕的 View 快取到 mCachedViews 裡面
                recycleByLayoutState(recycler, layoutState);
            }

            if (stopOnFocusable && layoutChunkResult.mFocusable) {
                break;
            }
        }
        return start - layoutState.mAvailable;
    }
    
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        // 從 Adapter 或回收池中獲取一個 View
        View view = layoutState.next(recycler);
        if (view == null) {
            result.mFinished = true;
            return;
        }
        
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        // 新增子 View 到 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);
            }
        }
        
        // 執行子 View 的 measure 流程
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        
        ...
        
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        result.mFocusable = view.hasFocusable();
    }
    
    static class LayoutState {
    
        List<RecyclerView.ViewHolder> mScrapList = null;
    
        /**
         * Gets the view for the next element that we should layout.
         * Also updates current item index to the next item, based on {@link #mItemDirection}
         */
        View next(RecyclerView.Recycler recycler) {
            if (mScrapList != null) { // 只在特定時候才不為空,省略
                return nextViewFromScrapList();
            }
            // 從 Adapter 或回收池中獲取一個 View
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            return view;
        }
        
    }
    
}
複製程式碼

可以看到,fill 方法的作用就是迴圈獲取一個個子 View,並載入到螢幕上,直到螢幕沒有剩餘空間或多餘的子 View,具體執行的方法是 layoutChunk,它首先呼叫 getViewForPosition 嘗試獲取一個子 View,接著呼叫 addView 方法把它載入到 RecyclerView 中,最後再執行子 View 的 measure 流程。同時,它會根據 LayoutState 判斷 RecyclerView 是否正在滾動中,如果是,則會把移出螢幕之外的子 View 快取以便之後重用。下面看看它是如何實現的,先從 getViewForPosition 開始分析:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    public final class Recycler {
    
         /**
         * Obtain a view initialized for the given position.
         *
         * This method should be used by {@link LayoutManager} implementations to obtain
         * views to represent data from an {@link Adapter}.
         * <p>
         * The Recycler may reuse a scrap or detached view from a shared pool if one is
         * available for the correct view type. If the adapter has not indicated that the
         * data at the given position has changed, the Recycler will attempt to hand back
         * a scrap view that was previously initialized for that data without rebinding.
         */
        public View getViewForPosition(int position) {
            return getViewForPosition(position, false);
        }
        
        View getViewForPosition(int position, boolean dryRun) {
            return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
        }
        
        /**
         * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
         * cache, the RecycledViewPool, or creating it directly.
         */
        @Nullable
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            boolean fromScrapOrHiddenOrCache = false;
            ViewHolder holder = null;
            // 0) 如果是預佈局,則嘗試從 mChangedScrap 中獲取
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 1) 根據 position 依次從 mAttachedScrap, mCachedViews 中獲取
            // 1) Find by position from scrap/hidden list/cache
            if (holder == null) {
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle holder (and unscrap if relevant) since it can't be used
                        // 這個 View 會被重用,從快取池中移除它
                        if (holder.isScrap()) {
                            removeDetachedView(holder.itemView, false);
                            holder.unScrap();
                        }
                        if (!dryRun) {
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrapOrHiddenOrCache = true;
                    }
                }
            }
            if (holder == null) {
                final int type = mAdapter.getItemViewType(offsetPosition);
                // 2) 如果上一步沒有獲取到,則根據 id 依次從 mAttachedScrap, mCachedViews 中獲取
                // 2) Find from scrap/cache via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    // 和 getScrapOrHiddenOrCachedHolderForPosition 類似,省略具體程式碼
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrapOrHiddenOrCache = true;
                    }
                }

                if (holder == null && mViewCacheExtension != null) {
                    // 從 mViewCacheExtension 中獲取
                    // We are NOT sending the offsetPosition because LayoutManager does not know it.
                    final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        ...
                    }
                }

                if (holder == null) { // fallback to pool
                    // 還沒有獲取到,則嘗試從 RecycledViewPool 中獲取
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                    }
                }
                if (holder == null) {
                    // 沒有可以複用的 View,則回撥 mAdapter 的 onCreateViewHolder 方法載入一個子 View
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    ...
                }
            }

            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // 如果是預佈局,則設定子 View 的 position 資訊即可
                // do not update unless we absolutely have to.
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                // 否則回撥 Adapter 的 onBindViewHolder 方法,為子 View 繫結資料
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }

            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if (!checkLayoutParams(lp)) {
                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else {
                rvLayoutParams = (LayoutParams) lp;
            }
            rvLayoutParams.mViewHolder = holder;
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
            return holder;
        }
        
        ViewHolder getChangedScrapViewForPosition(int position) {
            ...
            // find by position
            for (int i = 0; i < changedScrapSize; i++) {
            	// 從 mChangedScrap 中獲取快取的 View
                final ViewHolder holder = mChangedScrap.get(i);
                if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    return holder;
                }
            }
            // find by id
            if (mAdapter.hasStableIds()) {
                ...
            }
            return null;
        }
        
        /**
         * Returns a view for the position either from attach scrap, hidden children, or cache.
         *
         * @param dryRun  Does a dry run, finds the ViewHolder but does not remove
         */
        ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
            final int scrapCount = mAttachedScrap.size();

            // Try first for an exact, non-invalid match from scrap.
            for (int i = 0; i < scrapCount; i++) {
                final ViewHolder holder = mAttachedScrap.get(i);
                if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
                        && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    return holder;
                }
            }

            if (!dryRun) {
                // 作用是把之前隱藏的 View 重新拿出來,這裡不把它算作快取機制的一部分
                View view = mChildHelper.findHiddenNonRemovedView(position);
                if (view != null) {
                    // This View is good to be used. We just need to unhide, detach and move to the
                    // scrap list.
                    final ViewHolder vh = getChildViewHolderInt(view);
                    mChildHelper.unhide(view);
                    ...
                    mChildHelper.detachViewFromParent(layoutIndex);
                    scrapView(view);
                    return vh;
                }
            }

            // Search in our first-level recycled view cache.
            final int cacheSize = mCachedViews.size();
            for (int i = 0; i < cacheSize; i++) {
                final ViewHolder holder = mCachedViews.get(i);
                // invalid view holders may be in cache if adapter has stable ids as they can be
                // retrieved via getScrapOrCachedViewForId
                if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
                    // 從快取池中移除,getViewForPosition 中傳過來的 dryRun 值為 false
                    if (!dryRun) {
                        mCachedViews.remove(i);
                    }
                    return holder;
                }
            }
            return null;
        }
        
        private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
                int position, long deadlineNs) {
            ...
            mAdapter.bindViewHolder(holder, offsetPosition);
            ...
            return true;
        }
        
    }
    
    public abstract static class Adapter<VH extends ViewHolder> {
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
        
        public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
            ...
            	// 從 xml 資源中載入子 View 並建立 ViewHolder,這是我們重寫 Adapter 時的關鍵方法之一
                final VH holder = onCreateViewHolder(parent, viewType);
                holder.mItemViewType = viewType;
                return holder;
            ...
        }
        
        public final void bindViewHolder(@NonNull VH holder, int position) {
            holder.mPosition = position;
            if (hasStableIds()) {
                holder.mItemId = getItemId(position);
            }
            // 繫結資料到 ViewHolder,這是我們重寫 Adapter 時的關鍵方法之一
            onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
            ...
        }
        
    }
    
}
複製程式碼

可以看到,getViewForPosition 最終是按照一個固定的順序從 Recycler 或 RecycledViewPool 中一個個嘗試獲取的,流程為:mChangedScrap -> mAttachedScrap -> mCachedViews -> mViewCacheExtension -> RecycledViewPool,如果沒有可用的快取,則會呼叫 Adapter 的 onCreateViewHolder 方法從 xml 資源中載入子 View。

成功獲取到一個 itemView 之後,如果當前屬於預佈局階段,則設定 itemView 的位置資訊即可;否則呼叫 Adapter 的 onBindViewHolder 繫結資料到 itemView 中。接著就可以呼叫 addView 方法把 itemView 載入到 RecyclerView 中了:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    public abstract static class LayoutManager {
        
        public void addView(View child) {
            addView(child, -1);
        }
        
        public void addView(View child, int index) {
            addViewInt(child, index, false);
        }
        
        public void addDisappearingView(View child) {
            addDisappearingView(child, -1);
        }
        
        public void addDisappearingView(View child, int index) {
            addViewInt(child, index, true);
        }
        
        private void addViewInt(View child, int index, boolean disappearing) {
            final ViewHolder holder = getChildViewHolderInt(child);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (holder.wasReturnedFromScrap() || holder.isScrap()) { 
            	// 從快取池中重新拿出來的子 View, attach 即可
                mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
            } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
            	// 仍然在螢幕上顯示的子 View,移動到新的位置即可
                if (currentIndex != index) {
                    mRecyclerView.mLayout.moveView(currentIndex, index);
                }
            } else { 
            	// 新的子 View,新增到 RecyclerView 上
                mChildHelper.addView(child, index, false);
            }
            if (lp.mPendingInvalidate) {
                holder.itemView.invalidate();
                lp.mPendingInvalidate = false;
            }
        }
        
        /**
         * Moves a View from one position to another.
         */
        public void moveView(int fromIndex, int toIndex) {
            View view = getChildAt(fromIndex);
            detachViewAt(fromIndex);
            attachView(view, toIndex);
        }
        
    }
    
}
複製程式碼

前面說過,如果當前正在滾動螢幕,那麼 fill 方法還會呼叫 recycleByLayoutState 方法快取移出螢幕之外的子 View:

public class LinearLayoutManager extends RecyclerView.LayoutManager implements
        ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {

    private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (!layoutState.mRecycle || layoutState.mInfinite) {
            return;
        }
        // 根據滑動的距離計算哪些 View 需要被移除
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
        } else {
            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
        }
    }
    
    private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
        final int childCount = getChildCount();
        final int limit = mOrientationHelper.getEnd() - dt;
        if (mShouldReverseLayout) {
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedStart(child) < limit
                        || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                    // 快取
                    // stop here
                    recycleChildren(recycler, 0, i);
                    return;
                }
            }
        } else {
            ...
        }
    }
    
    private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
        ...
        // 移除並快取
        if (endIndex > startIndex) {
            for (int i = endIndex - 1; i >= startIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        } else {
            for (int i = startIndex; i > endIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        }
    }
    
}
複製程式碼
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    public abstract static class LayoutManager {
    
        public void removeAndRecycleViewAt(int index, Recycler recycler) {
            final View view = getChildAt(index);
            // 從 RecyclerView 中移除螢幕之外的子 View
            removeViewAt(index);
            // 移除後快取
            recycler.recycleView(view);
        }
        
        public void removeViewAt(int index) {
            final View child = getChildAt(index);
            if (child != null) {
                mChildHelper.removeViewAt(index);
            }
        }
        
    }
    
    public final class Recycler {
    
        public void recycleView(View view) {
            // This public recycle method tries to make view recycle-able since layout manager
            // intended to recycle this view (e.g. even if it is in scrap or change cache)
            ViewHolder holder = getChildViewHolderInt(view);
            if (holder.isTmpDetached()) {
                removeDetachedView(view, false);
            }
            if (holder.isScrap()) {
                // 把 ViewHolder 從 mChangedScrap 或 mAttachedScrap 中移除
                holder.unScrap();
            } else if (holder.wasReturnedFromScrap()) {
                holder.clearReturnedFromScrapFlag();
            }
            // 這個方法之前分析過,用於快取 ViewHolder 到 mCachedViews 中
            recycleViewHolderInternal(holder);
        }
        
        void unscrapView(ViewHolder holder) {
        	// 從 mChangedScrap 或 mAttachedScrap 中移除
            if (holder.mInChangeScrap) {
                mChangedScrap.remove(holder);
            } else {
                mAttachedScrap.remove(holder);
            }
            holder.mScrapContainer = null;
            holder.mInChangeScrap = false;
            holder.clearReturnedFromScrapFlag();
        }
        
    }
    
    public abstract static class ViewHolder {
    
        private Recycler mScrapContainer = null;
    
        void unScrap() {
            mScrapContainer.unscrapView(this);
        }
        
    }
    
}
複製程式碼

可以看到,fill 最終會把移除螢幕之外的子 View 快取到 mCahcedViews 裡面,如果這個 View 已經快取到了 mChangedScrap 或 mAttachedScrap 中,那麼還會把它移除出來,轉移到 mCahcedViews 裡面。

從上面的分析中可以看出,RecyclerView 的工作機制和 ListView 是很像的:

  1. 同樣在第一次 layout 時回撥 Adapter 的相關方法從 xml 資源中載入子 View
  2. 同樣會在第二第三次 layout 的先 remove / detach 所有的子 View,最後再 add / attach 回來,這是為了避免重複載入相同的子 View
  3. 同樣只載入一個手機螢幕能顯示的子 View
  4. 同樣使用多個快取陣列/列表,且不同的快取陣列/列表對應的功能不同,RecylerView 的 mAttachedScrap 對應 ListView 的 mActiveViews,mCahcedViews 對應 mScrapViews

區別在於:

  1. 除了 attach / detach 之外,RecyclerView 還有可能會 remove 子 View 再 add 回來,ListView 只會 detach 再 attach
  2. RecyclerView 的子 View 快取列表分工更細,共有 5 級快取,即 mChangedScrap、mAttachedScrap、mCahcedViews、mViewCacheExtension、RecycledViewPool,其中 mViewCacheExtension 用於提供給使用者自定義快取邏輯,而 RecycledViewPool 甚至可以提供給多個 RecyclerView 共用;ListView 則只分為 mActiveViews 和 mScrapViews 兩個級別
  3. RecyclerView 的快取單位是 ViewHolder,ListView 的快取單位是 View,ListView 需要配合 setTag 方法才能實現複用

RecyclerView 的快取機制在這裡就基本分析完成了,下面繼續看 dispatchLayoutStep3。

dispatchLayoutStep3

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    /**
     * The final step of the layout where we save the information about views for animations,
     * trigger animations and do any necessary cleanup.
     */
    private void dispatchLayoutStep3() {
        ...
        // 重置 LayoutState
        mState.mLayoutStep = State.STEP_START;
        if (mState.mRunSimpleAnimations) {
            // Step 3: Find out where things are now, and process change animations.
            for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
                ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                if (holder.shouldIgnore()) {
                    continue;
                }
                long key = getChangedHolderKey(holder);
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPostLayoutInformation(mState, holder);
                ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
                if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
                    // run a change animation
                    if (oldDisappearing && oldChangeViewHolder == holder) {
                        // run disappear animation instead of change
                        mViewInfoStore.addToPostLayout(holder, animationInfo);
                    } else {
                        final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
                                oldChangeViewHolder);
                        // we add and remove so that any post info is merged.
                        mViewInfoStore.addToPostLayout(holder, animationInfo);
                        ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
                        if (preInfo == null) {
                            handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
                        } else {
                            // 執行動畫
                            animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
                                    oldDisappearing, newDisappearing);
                        }
                    }
                } else {
                    mViewInfoStore.addToPostLayout(holder, animationInfo);
                }
            }

            // 執行 mViewInfoProcessCallback 裡的動畫
            // Step 4: Process view info lists and trigger animations
            mViewInfoStore.process(mViewInfoProcessCallback);
        }

        // 把 mAttachedScrap 裡快取的 View 移動到 mCachedViews 裡面
        mLayout.removeAndRecycleScrapInt(mRecycler);
        if (mRecycler.mChangedScrap != null) {
            mRecycler.mChangedScrap.clear();
        }
        
        if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
            // 把 mCachedViews 多餘的 View 移動到 RecycledViewPool 裡面
            mRecycler.updateViewCacheSize();
        }

        mLayout.onLayoutCompleted(mState);
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
        mViewInfoStore.clear();
        if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
            dispatchOnScrolled(0, 0);
        }
        recoverFocusFromState();
        resetFocusInfo();
    }
    
    private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
            @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo,
            boolean oldHolderDisappearing, boolean newHolderDisappearing) {
        ...
        if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) {
            postAnimationRunner();
        }
    }
    
    /**
     * The callback to convert view info diffs into animations.
     */
    private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
            new ViewInfoStore.ProcessCallback() {
                @Override
                public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
                        @Nullable ItemHolderInfo postInfo) {
                    mRecycler.unscrapView(viewHolder);
                    animateDisappearance(viewHolder, info, postInfo);
                }
                @Override
                public void processAppeared(ViewHolder viewHolder,
                        ItemHolderInfo preInfo, ItemHolderInfo info) {
                    animateAppearance(viewHolder, preInfo, info);
                }
                // 各種動畫最終都呼叫了 postAnimationRunner 執行
                ...
            };
    
    void animateDisappearance(@NonNull ViewHolder holder,
            @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
        if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
            postAnimationRunner();
        }
    }
    
     /**
     * Post a runnable to the next frame to run pending item animations. Only the first such
     * request will be posted, governed by the mPostedAnimatorRunner flag.
     */
    void postAnimationRunner() {
        if (!mPostedAnimatorRunner && mIsAttached) {
            ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
            mPostedAnimatorRunner = true;
        }
    }
    
    // DefaultItemAnimator 是 RecyclerView 預設的動畫執行者
    ItemAnimator mItemAnimator = new DefaultItemAnimator();
    
    private Runnable mItemAnimatorRunner = new Runnable() {
        @Override
        public void run() {
            if (mItemAnimator != null) {
                mItemAnimator.runPendingAnimations();
            }
            mPostedAnimatorRunner = false;
        }
    };
    
    public abstract static class LayoutManager {
    
        /**
         * Recycles the scrapped views.
         */
        void removeAndRecycleScrapInt(Recycler recycler) {
            final int scrapCount = recycler.getScrapCount();
            // Loop backward, recycler might be changed by removeDetachedView()
            for (int i = scrapCount - 1; i >= 0; i--) {
                final View scrap = recycler.getScrapViewAt(i);
                final ViewHolder vh = getChildViewHolderInt(scrap);
                if (vh.isTmpDetached()) {
                    mRecyclerView.removeDetachedView(scrap, false);
                }
                if (mRecyclerView.mItemAnimator != null) {
                    mRecyclerView.mItemAnimator.endAnimation(vh);
                }
                // 放入 mCachedViews 裡面
                recycler.quickRecycleScrapView(scrap);
            }
            recycler.clearScrap();
            if (scrapCount > 0) {
                mRecyclerView.invalidate();
            }
        }
        
        int getScrapCount() {
            return mAttachedScrap.size();
        }
        
        View getScrapViewAt(int index) {
            return mAttachedScrap.get(index).itemView;
        }
        
    }
    
}
複製程式碼

可以看到,dispatchLayoutStep3 的主要作用就是執行動畫和做一些清理工作,預設的動畫執行者是 DefaultItemAnimator,清理工作包括把 mAttachedScrap 裡快取的 View 移動到 mCachedViews 裡面,如果 mCachedViews 容量超出限制,則移動一部分到 RecycledViewPool 裡,同時清理 mChangedScrap 的所有元素。

滑動載入更多資料

經過上面的分析,可以基本肯定 RecyclerView 在滑動載入更多資料時,會呼叫 fill 方法使用 mCachedViews 進行子 View 的快取和複用,下面來驗證一下:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        ...
        switch (action) {
            ...
            case MotionEvent.ACTION_MOVE: {
                ...
                if (mScrollState == SCROLL_STATE_DRAGGING) {
                    mLastTouchX = x - mScrollOffset[0];
                    mLastTouchY = y - mScrollOffset[1];

                    if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            vtev)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    if (mGapWorker != null && (dx != 0 || dy != 0)) {
                        mGapWorker.postFromTraversal(this, dx, dy);
                    }
                }
            } break;
            ...
        }

        return true;
    }
    
    boolean scrollByInternal(int x, int y, MotionEvent ev) {
        int unconsumedX = 0, unconsumedY = 0;
        int consumedX = 0, consumedY = 0;

        consumePendingUpdateOperations();
        if (mAdapter != null) {
            ...
            // 滾動的事件處理由 LayoutManager 完成
            if (x != 0) {
                consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                unconsumedX = x - consumedX;
            }
            if (y != 0) {
                consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                unconsumedY = y - consumedY;
            }
        }
        
        if (!mItemDecorations.isEmpty()) {
            invalidate();
        }

        if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
                TYPE_TOUCH)) {
            ...
        } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
            ...
        }
        return consumedX != 0 || consumedY != 0;
    }
    
    // 如果 Adapter 更新了,則重新走一遍 layout 流程
    void consumePendingUpdateOperations() {
        if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
            dispatchLayout();
            return;
        }
        if (!mAdapterHelper.hasPendingUpdates()) {
            return;
        }

        // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
        // of the visible items is affected and if not, just ignore the change.
        if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
                .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
                        | AdapterHelper.UpdateOp.MOVE)) {
            if (!mLayoutWasDefered) {
                if (hasUpdatedView()) {
                    dispatchLayout();
                } else {
                    // no need to layout, clean state
                    mAdapterHelper.consumePostponedUpdates();
                }
            }
        } else if (mAdapterHelper.hasPendingUpdates()) {
            dispatchLayout();
        }
    }
    
}
複製程式碼
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
        ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            return 0;
        }
        return scrollBy(dx, recycler, state);
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == HORIZONTAL) {
            return 0;
        }
        return scrollBy(dy, recycler, state);
    }
    
    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || dy == 0) {
            return 0;
        }
        mLayoutState.mRecycle = true;
        ...
        // fill 在上面分析過,即迴圈獲取子 View 直到螢幕填充完畢,或子 View 消耗完畢
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
        ...
        return scrolled;
    }
    
}
複製程式碼

可以看到,就像上面的推測的那樣,RecyclerView 在滑動過程中會呼叫 fill 方法,使用 mCachedViews 進行子 View 的快取和複用,如果 Adapter 更新了,則會重新走了一遍 layout 的流程。

Adapter

上面可以說已經把 RecyclerView最關鍵的部分都分析完成了,但還有一些問題是沒有解決的,即 RecyclerView 是如何得知 Adapter 更新的?下面開始分析這個問題。

從 RecyclerView 的 setAdapter 方法開始看起:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
    Adapter mAdapter;
    AdapterHelper mAdapterHelper;

    public void setAdapter(Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        processDataSetCompletelyChanged(false);
        requestLayout();
    }
    
    private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        ...
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        ...
    }

    void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
        mDispatchItemsChangedEvent |= dispatchItemsChanged;
        // 標記 DataSet 改變了,之後會呼叫 dispatchLayout 走一遍 layout 流程
        mDataSetHasChangedAfterLayout = true;
        markKnownViewsInvalid();
    }

    public abstract static class Adapter<VH extends ViewHolder> {
        // 使用了觀察者模式
        private final AdapterDataObservable mObservable = new AdapterDataObservable();

        @NonNull
        public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);

        public abstract void onBindViewHolder(@NonNull VH holder, int position);
        
        ...

        // 註冊觀察者
        public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
            mObservable.registerObserver(observer);
        }

        public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) {
            mObservable.unregisterObserver(observer);
        }

        // 通知觀察者,資料發生了改變
        public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }

        public final void notifyItemInserted(int position) {
            mObservable.notifyItemRangeInserted(position, 1);
        }

        ...
        
    }
    
    static class AdapterDataObservable extends Observable<AdapterDataObserver> {

		// 通知所有觀察者
        public void notifyChanged() {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }

        public void notifyItemRangeInserted(int positionStart, int itemCount) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
            }
        }

        ...
    }
    
    private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {
        }

        @Override
        public void onChanged() {
            processDataSetCompletelyChanged(true);
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();
            }
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
                triggerUpdateProcessor();
            }
        }
        
        void triggerUpdateProcessor() {
            // 如果設定了 mHasFixedSize,那麼可能會省略一些工作
            if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                // 否則需要重新走一遍 View 的三大流程
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }

    }
    
    final Runnable mUpdateChildViewsRunnable = new Runnable() {
        @Override
        public void run() {
            if (!mFirstLayoutComplete || isLayoutRequested()) {
                // a layout request will happen, we should not do layout here.
                return;
            }
            if (!mIsAttached) {
                requestLayout();
                // if we are not attached yet, mark us as requiring layout and skip
                return;
            }
            if (mLayoutFrozen) {
                mLayoutWasDefered = true;
                return; //we'll process updates when ice age ends.
            }
            consumePendingUpdateOperations();
        }
    };
    
    void consumePendingUpdateOperations() {
        // 重新執行一遍 dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3
        if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
            dispatchLayout();
            return;
        }
        if (!mAdapterHelper.hasPendingUpdates()) {
            return;
        }

        // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
        // of the visible items is affected and if not, just ignore the change.
        if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
                .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
                        | AdapterHelper.UpdateOp.MOVE)) { // 區域性更新
            // 最終呼叫了 AdapterHelper 的 Callback 方法,Callback 的實現可以檢視 RecyclerView 的 initAdapterManager 方法
            mAdapterHelper.preProcess();
            if (!mLayoutWasDefered) {
                if (hasUpdatedView()) {
                    dispatchLayout();
                } else {
                    // no need to layout, clean state
                    mAdapterHelper.consumePostponedUpdates();
                }
            }
        } else if (mAdapterHelper.hasPendingUpdates()) {
            dispatchLayout();
        }
    }

}
複製程式碼

根據上面的分析,可以知道:

  1. RecyclerView 和 Adapter 之間是觀察者、被觀察者的關係,當 Adapter 呼叫了 notifyDataSetChanged 等方法時,RecyclerView 能夠得知這個變化,並執行 layout 等相應的行為
  2. 如果 ReccylerView 設定了 mHasFixedSize,那麼可以在 Adapter 被修改時執行 RecyclerView 的區域性更新,從而避免重新走一遍三大流程,提高效率

總結

RecyclerView 的 measure 和 layout 分為三步:

  1. dispatchLayoutStep1,負責 Adpater 更新、計算並儲存子 View 和動畫的相關資訊、預佈局(如果有必要)
  2. dispatchLayoutStep2,負責實際上的佈局,具體工作是交給 LayoutManager 完成的,但基本流程都一樣,都有相同的子 View 複用機制
  3. dispatchLayoutStep3,負責執行動畫及清理工作,預設的動畫執行者是 DefaultItemAnimator

對比 RecyclerView 和 ListView:

  1. 同樣在第一次 layout 時回撥 Adapter 的相關方法從 xml 資源中載入子 View
  2. 同樣會在第二第三次 layout 的先 remove / detach 所有的子 View,最後再 add / attach 回來,這是為了避免重複載入相同的子 View
  3. 同樣只載入一個手機螢幕能顯示的子 View
  4. 同樣使用多個快取陣列/列表,且不同的快取陣列/列表對應的功能不同,RecylerView 的 mAttachedScrap 對應 ListView 的 mActiveViews,mCahcedViews 對應 mScrapViews
  5. 同樣使用了觀察者模式,在 Adapter 呼叫了 notifyDataSetChanged 等方法時,能夠做出相應的改變

區別在於:

  1. 除了 attach / detach 之外,RecyclerView 還有可能會 remove 子 View 再 add 回來,ListView 只會 detach 再 attach
  2. RecyclerView 的子 View 快取列表分工更細,共有 5 級快取,即 mChangedScrap、mAttachedScrap、mCahcedViews、mViewCacheExtension、RecycledViewPool,其中 mViewCacheExtension 用於提供給使用者自定義快取邏輯,而 RecycledViewPool 甚至可以提供給多個 RecyclerView 共用;ListView 則只分為 mActiveViews 和 mScrapViews 兩個級別
  3. RecyclerView 的快取單位是 ViewHolder,ListView 的快取單位是 View,ListView 需要配合 setTag 方法才能實現複用

相關文章