RecyclerView源碼學(xué)習(xí)筆記

RecyclerView包含以下幾個重要的組件:
1.LayoutManager: 測量和布局子View
2.Recycler: View的緩存瓮栗、復(fù)用
3.ViewHolder: 對itemView及其元數(shù)據(jù)的封裝
4.ItemAnimator: 動畫
5.Adapter: 創(chuàng)建ViewHolder蚣抗、綁定數(shù)據(jù)、通知數(shù)據(jù)變更
6.ItemDecoration: ItemView的裝飾
7.SmoothScroller: 平滑滾動
8.ViewFlinger: 功能未知...

先看看最基本也最重要的Adapter融蹂。
RecyclerView.Adapter
在RecyclerView中,Adapter是其內(nèi)部的一個抽象類,咱們最熟悉的兩個方法: onCreateViewHolder 和 onBindViewHolder迄本,分別在 createViewHolder 和 bindViewHolder 中被調(diào)用。

public final VH createViewHolder(ViewGroup parent, int viewType) {
            TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
            final VH holder = onCreateViewHolder(parent, viewType);
            holder.mItemViewType = viewType;
            TraceCompat.endSection();
            return holder;
        }

這里直接調(diào)用咱們復(fù)寫的onCreateViewHolder课竣,傳進去兩個參數(shù)嘉赎。
ViewGroup: 當前View綁定到Adapter的position后添加到的ViewGroup
ViewType: 當前View的類型(這個類型由咱們自己定義,有時候一個列表需要有各種奇形怪狀的item稠氮,有方的曹阔,有圓的,都是不同的類型)

public final void bindViewHolder(VH holder, int position) {
            holder.mPosition = position;
            if (hasStableIds()) {
                holder.mItemId = getItemId(position);
            }
            holder.setFlags(ViewHolder.FLAG_BOUND,
                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
                            | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
            TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
            onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
            holder.clearPayload();
            final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
            if (layoutParams instanceof RecyclerView.LayoutParams) {
                ((LayoutParams) layoutParams).mInsetsDirty = true;
            }
            TraceCompat.endSection();
        }

這個也很簡單隔披,先記錄了當前Holder的position和id(如果設(shè)了hasStableId赃份,這個究竟是什么先按下不表,因為現(xiàn)在我還不知道...),然后設(shè)了Holder的Flag狀態(tài)抓韩,標記為綁定狀態(tài)纠永,調(diào)用咱們的onBindViewHolder,置LayoutParams為Dirty(即數(shù)據(jù)已變更谒拴,需要重繪)尝江。

除了上面兩個方法,還有比較容易理解的 getItemCount 和 getItemId, getItemViewType, 這幾個不提也罷英上。
onViewRecycled, 當被創(chuàng)建的一個view被復(fù)用的時候被調(diào)用炭序。就是,LayoutManager認為這個View沒有價值了苍日,比如在屏幕上不可見惭聂,就會復(fù)用這個View并且調(diào)用這個方法,可以在這里對該View進行資源釋放相恃。
然后辜纲,還有一個詭異的setHasStableId,設(shè)這個為 true 可以在notifyDataChange的時候提升效率拦耐,至于為什么耕腾,要等到后面看看notify的邏輯才能知曉了。

最后還有一個重頭戲杀糯,就是notify的一系列方法扫俺。notify的方法可以歸結(jié)為兩種類型,一種是列表結(jié)構(gòu)發(fā)生了變化固翰,一種是單個item發(fā)生變化牵舵。當只有單個item的數(shù)據(jù)更新時,列表的位置沒有變化倦挂,此時的變化屬于后者,notifyItemChanged担巩、notifyItemRangeChanged屬于此類方援。而當item位置發(fā)生改變,就是列表結(jié)構(gòu)的變化了涛癌,notifyItemInserted犯戏、notifyItemRangeInserted、notifyItemMoved拳话、notifyItemRemoved先匪、notifyItemRangeRemoved以及notifyDataSetChange屬于此類。注意弃衍,少調(diào)用notifyDataSetChange可以提升響應(yīng)速度呀非,因為這個方法會假設(shè)所有數(shù)據(jù)即位置信息都發(fā)生了變化,會重新綁定所有數(shù)據(jù),比較耗時岸裙,也無法執(zhí)行Item變化的默認動畫猖败。
下面要介紹一下Adapter中對觀察者模式的應(yīng)用,即View監(jiān)聽數(shù)據(jù)的變化降允。
在Adapter中有一個 Observable 全局變量恩闻,是 AdapterDataObservable 的實例。AdapterDataObservable 是一個標準的 Observable 實現(xiàn)剧董,其中包含了notify的一系列方法幢尚,在方法中使用for循環(huán)通知Observer數(shù)據(jù)發(fā)生了變化。那么訂閱動作發(fā)生在哪里呢翅楼?
來看看RecyclerView的setAdapter方法

public void setAdapter(Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        requestLayout();
    }

關(guān)鍵在這個setAdapterInternal里頭

private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            removeAndRecycleViews();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
        markKnownViewsInvalid();
    }

先忽略別的操作尉剩,可以看到,在這里犁嗅,先取消注冊了Observer边涕,然后重新注冊。Observer是 RecyclerViewDataObserver 的實現(xiàn)褂微。

private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {
        }

        @Override
        public void onChanged() {
            assertNotInLayoutOrScroll(null);
            mState.mStructureChanged = true;

            setDataSetChangedAfterLayout();
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();
            }
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
                triggerUpdateProcessor();
            }
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
                triggerUpdateProcessor();
            }
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
                triggerUpdateProcessor();
            }
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
                triggerUpdateProcessor();
            }
        }

        void triggerUpdateProcessor() {
            if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }
    }

在這個實現(xiàn)中功蜓,每一種變換都會先檢查當前沒有在布局或者滾動過程中,然后調(diào)用AdapterHelper的相應(yīng)方法宠蚂,最后 triggerUpdateProcessor. 在這個方法里可以看到式撼,滿足三個條件會直接執(zhí)行動畫,不滿足則需要重新布局求厕。
POST_UPDATES_ON_ANIMATION: 當前動作是否執(zhí)行動畫著隆,定義是SDK大于16.
mHasFixedSize: 這是一個RecyclerView可以設(shè)置的參數(shù),如果所有Item的大小一致呀癣,則可以直接置為true美浦。如果沒有置true,則需要重新布局项栏。
mIsAttached: 即RecyclerView是否attach到當前Window.
再看看這個執(zhí)行動畫的mUpdateChildViewsRunnable

/**
     * Note: this Runnable is only ever posted if:
     * 1) We've been through first layout
     * 2) We know we have a fixed size (mHasFixedSize)
     * 3) We're attached
     */
    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) {
                mLayoutRequestEaten = true;
                return; //we'll process updates when ice age ends.
            }
            consumePendingUpdateOperations();
        }
    };

注解和代碼都很清楚浦辨,滿足三個條件就會執(zhí)行consumePendingUpdateOperations方法。這個方法的意思是執(zhí)行當前那些被推遲執(zhí)行的更新操作沼沈,在里面調(diào)到了AdapterHelper的方法流酬。
這里又引出來一個AdapterHelper,定義里赫然寫著:處理Adapter更新列另。
看看這個類的介紹芽腾,它為每一個adapter的數(shù)據(jù)變化創(chuàng)建一個UpdateOps,然后對他們進行預(yù)處理页衙,決定哪些可以被推遲執(zhí)行摊滔,哪些不可以。對于要求立即執(zhí)行的UpdateOps,AdapterHelper會在第一次layout前根據(jù)上一個被推遲操作來對其進行更改惭载。由于操作的順序在這個過程中改變了旱函,所以它也處理被推遲的UpdateOps.
即使操作被以不同的順序轉(zhuǎn)發(fā)給LayoutManager,但數(shù)據(jù)是保證絕對正確的描滔。
那這些數(shù)據(jù)變更是怎么被轉(zhuǎn)發(fā)給LayoutManager的呢棒妨?
這里關(guān)鍵是它的內(nèi)部CallBack接口

/**
     * Contract between AdapterHelper and RecyclerView.
     */
    interface Callback {

        RecyclerView.ViewHolder findViewHolder(int position);

        void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);

        void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);

        void markViewHoldersUpdated(int positionStart, int itemCount, Object payloads);

        void onDispatchFirstPass(UpdateOp updateOp);

        void onDispatchSecondPass(UpdateOp updateOp);

        void offsetPositionsForAdd(int positionStart, int itemCount);

        void offsetPositionsForMove(int from, int to);
    }

流程是這樣的,consumePendingUpdateOperations 的描述是:滾動過程中的數(shù)據(jù)變更可能會導(dǎo)致Crash含长,滾動動作會假定沒有數(shù)據(jù)變更券腔。此方法消滅掉所有的延時變更來避免這個問題。
在這個方法中會調(diào)用AdapterHelper的preProcess預(yù)處理方法拘泞,preProcess會調(diào)用不同的apply方法(applyAdd, applyRemove等)纷纫,調(diào)到dispatchAndUpdateViewHolders,最終調(diào)用到RecyclerView中實現(xiàn)的Callback的dispatchUpdate方法來分發(fā)更新事件陪腌,在這里調(diào)用LayoutManager的不同方法辱魁,對UI做出更改。

void dispatchUpdate(AdapterHelper.UpdateOp op) {
                switch (op.cmd) {
                    case AdapterHelper.UpdateOp.ADD:
                        mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);
                        break;
                    case AdapterHelper.UpdateOp.REMOVE:
                        mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);
                        break;
                    case AdapterHelper.UpdateOp.UPDATE:
                        mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount,
                                op.payload);
                        break;
                    case AdapterHelper.UpdateOp.MOVE:
                        mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);
                        break;
                }
            }

那么以上就是RecyclerView使用觀察者模式诗鸭,在數(shù)據(jù)變更時通知UI更新的流程染簇。

OK,趁熱打鐵(其實已經(jīng)過了一晚上了...)强岸,咱們來看看ViewHolder吧(由于昨夜精神太好所以就Solo了一下锻弓,挑個簡單的提提神)。
ViewHolder
ViewHolder是一個對itemView蝌箍、item數(shù)據(jù)青灼、item類型的封裝。
同樣的妓盲,ViewHolder也是RecyclerView的一個內(nèi)部抽象類杂拨。先看看它為自己定義的狀態(tài):
FLAG_BOUND——ViewHolder已經(jīng)綁定到某個位置,mPosition悯衬、mItemId扳躬、mItemViewType都有效
FLAG_UPDATE——ViewHolder綁定的View對應(yīng)的數(shù)據(jù)過時需要重新綁定,mPosition甚亭、mItemId還是一致的
FLAG_INVALID——ViewHolder綁定的View對應(yīng)的數(shù)據(jù)無效,需要完全重新綁定不同的數(shù)據(jù)
FLAG_REMOVED——ViewHolder對應(yīng)的數(shù)據(jù)已經(jīng)從數(shù)據(jù)集移除
FLAG_NOT_RECYCLABLE——ViewHolder不能復(fù)用
FLAG_RETURNED_FROM_SCRAP——這個狀態(tài)的ViewHolder會加到scrap list被復(fù)用击胜。
FLAG_CHANGED——ViewHolder內(nèi)容發(fā)生變化亏狰,通常用于表明有ItemAnimator動畫
FLAG_IGNORE——ViewHolder完全由LayoutManager管理,不能復(fù)用
FLAG_TMP_DETACHED——ViewHolder從父RecyclerView臨時分離的標志偶摔,便于后續(xù)移除或添加回來
FLAG_ADAPTER_POSITION_UNKNOWN——ViewHolder不知道對應(yīng)的Adapter的位置暇唾,直到綁定到一個新位置
FLAG_ADAPTER_FULLUPDATE——方法addChangePayload(null)調(diào)用時設(shè)置

因為牽扯到View的復(fù)用,ViewHolder的狀態(tài)是很復(fù)雜的,后面在追到不同地方的時候會慢慢看到這些狀態(tài)的實際應(yīng)用策州。
在這里頭瘸味,position是個搞腦子的東西。在之前版本的RecyclerView中够挂,ViewHolder只有一個getPosition方法旁仿,后來被Deprecate了,注釋道:這個方法很模糊孽糖,position在某些情況下是會沖突的枯冈,然后給了兩個新方法,getLayoutPosition和getAdapterPosition. 注釋還說办悟,RecyclerView在下一次布局繪制前不會處理任何Adapter的更新尘奏,這就造成了一個問題:比如現(xiàn)在有一個Item的position是0,此時調(diào)用notifyItemInserted(0)病蛉,Adapter實際已經(jīng)更新了炫加,getAdapterPosition返回1,但是RecyclerView還沒有進行重繪铺然,那么它不處理任何接收到的更新俗孝,此時getLayoutPosition還是返回0. 就造成了Adapter的position和用戶實際看到的position不匹配的時間窗口,這個窗口注釋聲稱小于16ms探熔,那么這里如果調(diào)了錯誤的方法就可能出bug. 因為LayoutManager是管理UI的驹针,所以應(yīng)該調(diào)用的是getLayoutPosition,必須保證對UI的處理與用戶看到的一致诀艰。而對用戶事件的處理則應(yīng)該調(diào)getAdapterPosition柬甥,不然就可能出現(xiàn)用戶明明點的是第0位的item,第1位的item響應(yīng)了事件的bug.
ViewHolder值得說的也就這了...


下面看看LayoutManager吧其垄,本來想直接看Recycler的緩存機制的苛蒲,考慮到這個緩存機制跟LayoutManager息息相關(guān),所以咱們一個一個來绿满,把LayoutManager解決先臂外。

LayoutManager
LayoutManager應(yīng)該是RecyclerView中最復(fù)雜的組件了,作為一個抽象內(nèi)部類喇颁,洋洋灑灑三千行代碼...如開篇所說漏健,LayoutManager處理RecyclerView UI相關(guān)的功能,比如測量布局items橘霎、滾動頁面等等蔫浆。
幸運的是,v7包默認已經(jīng)幫咱們實現(xiàn)了三個LayoutManager姐叁,看著具體實現(xiàn)的話瓦盛,追源碼就不會那么累了洗显。這三個分別是 LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager. 分別實現(xiàn)了線性布局、table布局和一個不知道什么鬼東西的布局原环。Stagger本身有動態(tài)的意思挠唆,所以...動態(tài)table?...
先不扯實現(xiàn)了嘱吗,來看看LayoutManager這個抽象內(nèi)部類吧玄组。

/**
     * A <code>LayoutManager</code> is responsible for measuring and positioning item views
     * within a <code>RecyclerView</code> as well as determining the policy for when to recycle
     * item views that are no longer visible to the user. By changing the <code>LayoutManager</code>
     * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list,
     * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
     * layout managers are provided for general use.
     */

照顧一下英語不好的童鞋: LayoutManager用于測量和擺放itemViews,同時負責(zé)決定在什么時間點可以復(fù)用那些不被用戶看到的itemViews. 更改LayoutManager可以實現(xiàn)豎的柜与、橫的巧勤、grid的等等各種奇形怪狀的列表。
咱們都知道弄匕,RecyclerView也是一個ViewGroup(廢話)颅悉,它在擺放子View的時候會有測量和布局的流程,而這兩個操作都已經(jīng)托付給LayoutManager來完成了迁匠,那么肯定會有這兩個事件的傳遞過程剩瓶。
先來看看測量

@Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        if (mLayout.mAutoMeasure) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
            final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                    && heightMode == MeasureSpec.EXACTLY;
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            if (skipMeasure || mAdapter == null) {
                return;
            }
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }
            // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
            // consistency
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();

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

            // if RecyclerView has non-exact width and height and if there is at least one child
            // which also has non-exact width & height, we have to re-measure.
            if (mLayout.shouldMeasureTwice()) {
                mLayout.setMeasureSpecs(
                        MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } else {
            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            // custom onMeasure
            if (mAdapterUpdateDuringMeasure) {
                eatRequestLayout();
                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;
                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
        }
    }

看上去好像干了很多事情,我們就以這個方法為主干城丧,分析測量流程延曙。
開始之前先看一下mState,看名字就猜到這是用來記錄狀態(tài)的亡哄。注釋里把它稱為data bus枝缔,可見除了記錄狀態(tài),它還有記錄信息的作用蚊惯。RecyclerView各個組件間信息的傳遞也靠它了愿卸。里頭有一個mLayoutStep,這個值有三個狀態(tài)截型,分別是STEP_START趴荸、STEP_LAYOUT、STEP_ANIMATIONS宦焦,mLayoutStep初始值為STEP_START.
首先判了一下空发钝,mLayout就是LayoutManager的實例。如果為空波闹,使用默認的測量機制证逻。這個不管闭树。
然后接觸到第一個LayoutManager的可設(shè)參數(shù)胀莹,AutoMeasure铝侵,也就是自動測量。 注釋寫了一大段中學(xué)生作文锄码,總結(jié)一下就是:這個屬性決定了RecyclerView如何進行測量夺英。如果開啟AutoMeasure,那么RecyclerView將支持WRAP_CONTENT屬性滋捶,以包裹內(nèi)容為終極目標來執(zhí)行測量痛悯。如果不開啟,就要復(fù)寫LayoutManager的onMeasure方法來自定義測量方案重窟≡孛龋框架提供的三個LayoutManager實現(xiàn)都是用的這個自動測量機制。
關(guān)于這個自動測量機制是如何實現(xiàn)的巡扇,后面會專門分一塊出來細讀扭仁,現(xiàn)在咱還是緊跟步伐,假設(shè)我們現(xiàn)在有一個寬高固定的RecyclerView厅翔,默認進入自動測量乖坠,然后調(diào)了LayoutManager的onMeasure. 咱們以LinearLayoutManager為例,它并未實現(xiàn)onMeasure刀闷,在抽象類中onMeasure調(diào)用了defaultOnMeasure顽分,這個方法僅僅把寬度加上paddings翻默,高度加上paddings和泌,然后就setMeasuredDimension了,非常直截了當。
然后由于寬高確定忠烛,所以直接返回冤议。onLayout的調(diào)用緊隨其后恕酸。
onLayout操作直接調(diào)給了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();
    }

此方法第一次被調(diào)用時會依次執(zhí)行dispatchLayoutStep1() 凉翻、dispatchLayoutStep2() 和 dispatchLayoutStep3().
好铺罢,下面來挨個兒仔細看看layout的這三個步驟缩滨。


dispatchLayoutStep1

注釋:
1.處理adapter的更新
2.確定要執(zhí)行的動畫
3.保存當前views的信息
4.如果必要,運行可預(yù)測布局,并保存其信息

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

        if (mState.mRunSimpleAnimations) {
            //...
        }
        if (mState.mRunPredictiveAnimations) {
            //...
        } else {
            clearOldPositions();
        }
        onExitLayoutOrScroll();
        resumeRequestLayout(false);
        mState.mLayoutStep = State.STEP_LAYOUT;
    }

在此方法中置mState.mIsMeasuring = false,然后處理adapter的更新畴蹭。
來看看處理adapter更新的部分。

/**
     * Consumes adapter updates and calculates which type of animations we want to run.
     * Called in onMeasure and dispatchLayout.
     * <p>
     * This method may process only the pre-layout state of updates or all of them.
     */
    private void processAdapterUpdatesAndSetAnimationFlags() {
        if (mDataSetHasChangedAfterLayout) {
            // Processing these items have no value since data set changed unexpectedly.
            // Instead, we just reset it.
            mAdapterHelper.reset();
            mLayout.onItemsChanged(this);
        }
        // simple animations are a subset of advanced animations (which will cause a
        // pre-layout step)
        // If layout supports predictive animations, pre-process to decide if we want to run them
        if (predictiveItemAnimationsEnabled()) {
            mAdapterHelper.preProcess();
        } else {
            mAdapterHelper.consumeUpdatesInOnePass();
        }
        boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
        mState.mRunSimpleAnimations = mFirstLayoutComplete
                && mItemAnimator != null
                && (mDataSetHasChangedAfterLayout
                        || animationTypeSupported
                        || mLayout.mRequestedSimpleAnimations)
                && (!mDataSetHasChangedAfterLayout
                        || mAdapter.hasStableIds());
        mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
                && animationTypeSupported
                && !mDataSetHasChangedAfterLayout
                && predictiveItemAnimationsEnabled();
    }

上來就是一個判斷繁扎,問數(shù)據(jù)是否已經(jīng)更改糊闽,如果已更改梳玫,那么處理這些items就沒有意義了,直接reset. mDataSetHasChangedAfterLayout這個參數(shù)會在兩種情況下被置為true,分別是更換adapter的時候钓账,以及調(diào)用notifyDataSetChange的時候梆暮,這也解釋了上面講的調(diào)用這個方法會導(dǎo)致性能損失的現(xiàn)象啦粹。
然后判斷可預(yù)期item動畫是否開啟偿荷,這里再展開講一下可預(yù)期動畫。這個要返回true要滿足兩個條件唠椭,ItemAnimator不為空跳纳,且LayoutManager支持可預(yù)期動畫。LayoutManager默認返回false贪嫂,即不支持寺庄。注意,如果RecyclerView的ItemAnimator不為空力崇,LayoutManager的supportsPredictiveItemAnimations返回false斗塘,那么會自動開啟simple item animations,添加或移除views只進行簡單的淡入淡出動畫亮靴。如果ItemAnimator不為空且supportsPredictiveItemAnimations返回true馍盟,那么onLayoutChildren(Recycler, State)會被調(diào)用兩次,目的是為了記錄需要的信息來更智能地預(yù)測什么樣的動畫需要怎樣被執(zhí)行茧吊。
回過頭來贞岭,如果開啟了可預(yù)期item動畫,就會執(zhí)行adapterHelper的preProcess預(yù)處理方法饱狂,上面分析Adapter的時候已經(jīng)講過曹步,這個方法最終會調(diào)到RecyclerView實現(xiàn)的AdapterHelper的Callback接口的分發(fā)更新方法,最終執(zhí)行更新動畫休讳。
如果未開啟可預(yù)期動畫讲婚,則調(diào)用adapterHelper的consumeUpdatesInOnePass,不對更新操作進行預(yù)處理俊柔,直接回調(diào)到RecyclerView里的更新事件筹麸,傳遞給LayoutManager.
然后就是計算需要執(zhí)行的動畫了活合,這里要看一下兩個狀態(tài),mRunSimpleAnimations 和 mRunPredictiveAnimations物赶。

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

可以看到白指,mRunSimpleAnimations在滿足以下條件時為true:
1.mFirstLayoutComplete為true(onLayout第一次執(zhí)行完后被置為true)
2.mItemAnimator不為空
3.Layout后數(shù)據(jù)發(fā)生了變化 或 有item被移除或添加 或 LayoutManager請求執(zhí)行simple animations
4.Layout后數(shù)據(jù)不發(fā)生變化 或 mAdapter有穩(wěn)定的ID
而運行預(yù)期動畫mRunPredictiveAnimations則在以下條件被滿足時返回true:
1.mRunSimpleAnimations為true
2.有item添加或移除
3.Layout后數(shù)據(jù)未發(fā)生變化
4.預(yù)期Item動畫被開啟
回到step1,在記錄了一些狀態(tài)后酵紫,記錄了adapter當前items的count告嘲,置狀態(tài)mInPreLayout為執(zhí)行預(yù)期動畫的值,第一次調(diào)用為false奖地,記錄第一個和最后一個子View的位置信息橄唬。然后判斷是否運行simple animations 和 預(yù)期item動畫。由于第一次Layout尚未完成参歹,所以不會執(zhí)行仰楚。
step1的最后把State的mLayoutStep置為了STEP_LAYOUT.
layoutStep1執(zhí)行完畢后,調(diào)用了mLayout.setExactMeasureSpecsFrom(this);,把當前RecyclerView的絕對大小告知了LayoutManager. 然后馬上調(diào)用step2.


dispatchLayoutStep2:

注釋:
布局第二步對views進行真正的最終布局犬庇,確定最終狀態(tài)僧界。
如果有必要會被調(diào)用多次。

/**
     * 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() {
        eatRequestLayout();
        onEnterLayoutOrScroll();
        mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
        mAdapterHelper.consumeUpdatesInOnePass();
        mState.mItemCount = mAdapter.getItemCount();
        mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

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

        mState.mStructureChanged = false;
        mPendingSavedState = null;

        // onLayoutChildren may have caused client code to disable item animations; re-check
        mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
        mState.mLayoutStep = State.STEP_ANIMATIONS;
        onExitLayoutOrScroll();
        resumeRequestLayout(false);
    }

在這里首先調(diào)了mAdapterHelper的consumeUpdatesInOnePass臭挽,此方法跳過了預(yù)處理的階段捂襟,直接對adapter的更新進行處理。然后再次記錄了adapter的itemCount. 再然后埋哟,置狀態(tài)mInPreLayout標識為false笆豁,調(diào)用LayoutManager的onLayoutChildren(mRecycler, mState)方法執(zhí)行真正的Layout.
LayoutManager完成了布局子View后,dispatchLayoutStep2置狀態(tài)mLayoutStep為STEP_ANIMATIONS. 至此赤赊,LayoutStep2執(zhí)行完畢闯狱。


dispatchLayoutStep3

注釋:
Layout的最后一步,在這里保存views的信息用于動畫抛计。
觸發(fā)動畫哄孤,并且做好善后工作。

private void dispatchLayoutStep3() {
        mState.assertLayoutStep(State.STEP_ANIMATIONS);
        eatRequestLayout();
        onEnterLayoutOrScroll();
        mState.mLayoutStep = State.STEP_START;
        if (mState.mRunSimpleAnimations) {
            //...
        }

        mLayout.removeAndRecycleScrapInt(mRecycler);
        mState.mPreviousLayoutItemCount = mState.mItemCount;
        mDataSetHasChangedAfterLayout = false;
        mState.mRunSimpleAnimations = false;

        mState.mRunPredictiveAnimations = false;
        mLayout.mRequestedSimpleAnimations = false;
        if (mRecycler.mChangedScrap != null) {
            mRecycler.mChangedScrap.clear();
        }
        if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
            // Initial prefetch has expanded cache, so reset until next prefetch.
            // This prevents initial prefetches from expanding the cache permanently.
            mLayout.mPrefetchMaxCountObserved = 0;
            mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
            mRecycler.updateViewCacheSize();
        }

        mLayout.onLayoutCompleted(mState);
        onExitLayoutOrScroll();
        resumeRequestLayout(false);
        mViewInfoStore.clear();
        if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
            dispatchOnScrolled(0, 0);
        }
        recoverFocusFromState();
        resetFocusInfo();
    }

挑重要的看吹截。置mLayoutStep為STEP_START, 因為mRunSimpleAnimation依然為false瘦陈,所以不執(zhí)行判斷內(nèi)的代碼。然后reset了一堆狀態(tài)波俄,記錄當前itemCount為mPreviousLayoutItemCount. 最后晨逝,調(diào)用了LayoutManager的onLayoutCompleted,通知LayoutManager當前已經(jīng)完成了Layout操作懦铺。
至此捉貌,Step3執(zhí)行完畢。


以上就是第一次onLayout被調(diào)用的執(zhí)行流程。

那么如果是已經(jīng)完成了布局的RecyclerView趁窃,調(diào)用notifyItemRemoved()移除一個item時又是怎么走流程的呢牧挣?因為一個item被移除了,預(yù)期它要執(zhí)行一個淡出動畫醒陆,然后后面的Item上移這樣一個簡單的動作瀑构。
我們從notifyItemRemoved開始追,走一遍數(shù)據(jù)變更的完整流程刨摩。
觸發(fā)Item移除的操作后寺晌,首先跟到notifyItemRemoved方法內(nèi)部。

public final void notifyItemRemoved(int position) {
            mObservable.notifyItemRangeRemoved(position, 1);
        }

再到Observable看看:

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

跟預(yù)期是一致的澡刹,通知觀察者當前數(shù)據(jù)發(fā)生了變更折剃。

@Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
                triggerUpdateProcessor();
            }
        }

void triggerUpdateProcessor() {
            if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }

由于我設(shè)置了setHasFixedSize為true,所以直接運行mUpdateChildViewsRunnable.

/**
     * Note: this Runnable is only ever posted if:
     * 1) We've been through first layout
     * 2) We know we have a fixed size (mHasFixedSize)
     * 3) We're attached
     */
    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) {
                mLayoutRequestEaten = true;
                return; //we'll process updates when ice age ends.
            }
            consumePendingUpdateOperations();
        }
    };
void consumePendingUpdateOperations() {
        if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
            TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
            dispatchLayout();
            TraceCompat.endSection();
            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)) {
            TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
            eatRequestLayout();
            onEnterLayoutOrScroll();
            mAdapterHelper.preProcess();
            if (!mLayoutRequestEaten) {
                if (hasUpdatedView()) {
                    dispatchLayout();
                } else {
                    // no need to layout, clean state
                    mAdapterHelper.consumePostponedUpdates();
                }
            }
            resumeRequestLayout(true);
            onExitLayoutOrScroll();
            TraceCompat.endSection();
        } else if (mAdapterHelper.hasPendingUpdates()) {
            TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
            dispatchLayout();
            TraceCompat.endSection();
        }

在這里會直接調(diào)用dispatchLayout像屋,然后走布局三部曲。注意边篮,mRunSimpleAnimations這個參數(shù)的條件已經(jīng)滿足了(如果忘了就到上面去看看需要哪些條件己莺,懶得看就假裝它被滿足了吧),之前走第一遍流程的時候我們略過了跟它相關(guān)的代碼戈轿,這次來看看凌受。
dispatchLayoutStep1

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);
                    // This is NOT the only place where a ViewHolder is added to old change holders
                    // list. There is another case where:
                    //    * A VH is currently hidden but not deleted
                    //    * The hidden item is changed in the adapter
                    //    * Layout manager decides to layout the item in the pre-Layout pass (step1)
                    // When this case is detected, RV will un-hide that view and add to the old
                    // change holders list.
                    mViewInfoStore.addToOldChangeHolders(key, holder);
                }
            }
        }

直接就是一個for循環(huán),遍歷當前所有可見的子View的ViewHolder思杯,注意胜蛉,是可---見---的view的ViewHolder. 這個可見View的count來自于ChildHelper,來看看ChildHelper的getChildCount:

int getChildCount() {
        return mCallback.getChildCount() - mHiddenViews.size();
    }

這個Callback實現(xiàn)當然是在RecyclerView里頭色乾,這里返回的是RecyclerView的子View減去隱藏view的數(shù)量誊册。看好了暖璧,RecyclerView的childCount并不是所有數(shù)據(jù)Item count案怯,而是當前RecyclerView可見的Views的數(shù)量。比如當前澎办,我的主頁只可見一個item嘲碱,那么在這個時間點,RecyclerView的childCount就為1. 這就是為什么如果要獲取列表數(shù)量局蚀,要調(diào)用Adapter的getChildCount麦锯,不能調(diào)RecyclerView的getChildCount,因為后者是一個隨時在變化的動態(tài)值琅绅。
我的Demo中只有一個item可見扶欣,總共四個items. 所以只走了一次循環(huán),把這個holder添加到了preLayoutList,就出去了宵蛀。
mRunPredictiveAnimations同樣滿足條件昆著。

if (mState.mRunPredictiveAnimations) {
            // Step 1: run prelayout: This will use the old positions of items. The layout manager
            // is expected to layout everything, even removed items (though not to add removed
            // items back to the container). This gives the pre-layout position of APPEARING views
            // which come into existence as part of the real layout.

            // Save old positions so that LayoutManager can run its mapping logic.
            saveOldPositions();
            final boolean didStructureChange = mState.mStructureChanged;
            mState.mStructureChanged = false;
            // 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();
        }

注解都已經(jīng)說了,運行preLayout(預(yù)布局)术陶,會使用items的舊的positions. LayoutManager應(yīng)該Layout所有的東西凑懂,包括已經(jīng)被移除的items.
這里也是一個循環(huán),在循環(huán)前調(diào)用了LayoutManager的onLayout方法執(zhí)行了一次布局梧宫,在布局的時候動了手腳接谨。在LinearLayoutManager的實現(xiàn)中,偷偷地根據(jù)ViewHolder的標記位向RecyclerView添加了childView.

if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        }

這個addView調(diào)用的是抽象父類的addView方法塘匣。在抽象父類addView的實現(xiàn)中有這么一句:

mChildHelper.addView(child, index, false);

ChildHelper的addView:

mCallback.addView(child, offset);

RecyclerView中對Callback的實現(xiàn):

@Override
            public void addView(View child, int index) {
                if (VERBOSE_TRACING) {
                    TraceCompat.beginSection("RV addView");
                }
                RecyclerView.this.addView(child, index);
                if (VERBOSE_TRACING) {
                    TraceCompat.endSection();
                }
                dispatchChildAttached(child);
            }

是他是他就是他脓豪,就是在這里把子View添加到RecyclerView當中的。

void dispatchChildAttached(View child) {
        final ViewHolder viewHolder = getChildViewHolderInt(child);
        onChildAttachedToWindow(child);
        if (mAdapter != null && viewHolder != null) {
            mAdapter.onViewAttachedToWindow(viewHolder);
        }
        if (mOnChildAttachStateListeners != null) {
            final int cnt = mOnChildAttachStateListeners.size();
            for (int i = cnt - 1; i >= 0; i--) {
                mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child);
            }
        }
    }

哈忌卤,看見熟面孔了么扫夜,onChildAttachedToWindow就是在這兒調(diào)用的。
好驰徊,跑題有點遠笤闯。剛剛說到,Step1的預(yù)期布局判斷的for循環(huán)前調(diào)用了LayoutManager的onLayout棍厂,在這里面向RecyclerView添加了即將要顯示的子View. 這個循環(huán)會進行兩次颗味,拿到的第一個ViewHolder是上一次已經(jīng)被加到PreLayout的第0位View,第二個ViewHolder是新添加的牺弹,并沒有被hide浦马,所以調(diào)的是addToAppearedInPreLayoutHolders.
這里打斷一下,需要看一下這些holders都被塞到什么地方了张漂。這里有一個ViewInfoStore類晶默,注釋說,這個類抽象了所有運行動畫所需的View信息航攒。在這里維護了兩個集合荤胁,一個mLayoutHolderMap,用于存儲那些即將執(zhí)行動畫的holders和它們相對應(yīng)的信息的映射屎债,另一個mOldChangedHolders仅政,存儲已存在的發(fā)生了改變的ViewHolder.
來看看Step1調(diào)用的是什么方法。在第一個mRunSimpleAnimation的判斷中盆驹,調(diào)用的是addToPreLayout和addToOldChangeHolders兩個方法圆丹。

void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        record.preInfo = info;
        record.flags |= FLAG_PRE;
    }

將第0位item加到了mLayoutHolderMap中,并記錄標記為FLAG_PRE,代表的應(yīng)該是當前holder即將執(zhí)行動畫躯喇。

void addToOldChangeHolders(long key, ViewHolder holder) {
        mOldChangedHolders.put(key, holder);
    }

這個沒什么可說的辫封。來看看mRunPredictiveAnimation判斷的代碼硝枉,調(diào)用的是addToAppearedInPreLayoutHolders.

void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        record.flags |= FLAG_APPEAR;
        record.preInfo = info;
    }

這里也是存入了mLayoutHolderMap集合,但是置標記為FLAG_APPEAR倦微,意思應(yīng)該是即將執(zhí)行出現(xiàn)的動畫妻味。
好了,對于數(shù)據(jù)變更時Step1所執(zhí)行的操作應(yīng)該了然于心了欣福,Step2實質(zhì)上只是又進行了一次布局责球。直接看Step3.

if (mState.mRunSimpleAnimations) {
            // Step 3: Find out where things are now, and process change animations.
            // traverse list in reverse because we may call animateChange in the loop which may
            // remove the target view holder.
            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 an Item is CHANGED but the updated version is disappearing, it creates
                    // a conflicting case.
                    // Since a view that is marked as disappearing is likely to be going out of
                    // bounds, we run a change animation. Both views will be cleaned automatically
                    // once their animations finish.
                    // On the other hand, if it is the same view holder instance, we run a
                    // disappearing animation instead because we are not going to rebind the updated
                    // VH unless it is enforced by the layout manager.
                    final boolean oldDisappearing = mViewInfoStore.isDisappearing(
                            oldChangeViewHolder);
                    final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
                    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);
                }
            }

            // Step 4: Process view info lists and trigger animations
            mViewInfoStore.process(mViewInfoProcessCallback);
        }

這里先嘗試從ViewInfoStore中取出保存的這個Holder的OldHolder,由于現(xiàn)在布局中存在的僅僅是第1位holder拓劝,而我們之前加到oldChangeHolders的只有第0位雏逾,所以拿到的是空,直接到addToPostLayout. 這個方法跟上面的幾個類似郑临,添加到mLayoutHolderMap栖博,置標記為FLAG_POST. 然后執(zhí)行mViewInfoProcessCallback.
process方法根據(jù)flag判斷調(diào)用callback的哪個具體方法。這里首先調(diào)用的是processDisappeared, 執(zhí)行消失厢洞,在這里調(diào)用animateDisappearance.

void animateDisappearance(@NonNull ViewHolder holder,
            @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
        addAnimatingView(holder);
        holder.setIsRecyclable(false);
        if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
            postAnimationRunner();
        }
    }

addAnimationView:

private void addAnimatingView(ViewHolder viewHolder) {
        final View view = viewHolder.itemView;
        final boolean alreadyParented = view.getParent() == this;
        mRecycler.unscrapView(getChildViewHolder(view));
        if (viewHolder.isTmpDetached()) {
            // re-attach
            mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
        } else if(!alreadyParented) {
            mChildHelper.addView(view, true);
        } else {
            mChildHelper.hide(view);
        }
    }

呦仇让,還良心地講解了這個機制是如何執(zhí)行的,好好好躺翻,這就可以一邊看著代碼一邊看著注釋兌著灌了妹孙。
首先,RecyclerView的onMeasure被調(diào)用获枝,如果MeasureSpec為EXACT,則不進行測量骇笔,直接返回(寬高已經(jīng)被訂好了)省店。否則,開始在onMeasure中處理布局流程笨触。它會處理所有的待處理adapter更新懦傍,并決定是不是要進行預(yù)布局。如果要進行預(yù)布局芦劣,會將state.preLayout()置為true粗俱,然后調(diào)用onLayoutChildren(Recycler, State). 此時,getWidth與getHeight依然返回上一次layout的結(jié)果虚吟。
預(yù)處理完畢后寸认,會設(shè)state.preLayout為false,state.isMeasuring為true串慰,此時偏塞,LayoutManager就可以通過getHeight、getHeightMode來獲取測量的specs了邦鲫。
layout計算完后灸叼,RecyclerView為子view們計算邊界盒(加上padding的大猩裥凇),設(shè)置測量過的height和width. LayoutManager可以通過復(fù)寫setMeasuredDimension(Rect, int, int)來選擇不同的值古今。比如屁魏,GridLayoutManager復(fù)寫這個值來處理三列布局時單排顯示兩個items的width計算。
此之后onMeasure的所有調(diào)用都會把狀態(tài)置為isMeasuring. RecyclerView管理view的增刪改操作捉腥,LayoutManager啥都不用管氓拼,只要把每個onLayoutChildren調(diào)用當做最后一次調(diào)用就可以了。
測量結(jié)束后但狭,RecyclerView的onLayout(boolean, int, int, int, int)被調(diào)用披诗。RecyclerView檢查在測量過程中是否進行了布局計算,如果是立磁,則重用其相關(guān)的信息呈队。如果最后一次的measure spec與最終的大小不匹配,或adapter的數(shù)據(jù)在measure和layout過程中被更改唱歧,它也可能再次調(diào)用onLayoutChildren.
最后的最后宪摧,計算動畫然后執(zhí)行。
上面就是對測量流程的粗略描述颅崩。下面看下代碼几于。
onMeasure被調(diào)用時,mState.mLayoutStep默認為STEP_START沿后,開始布局沿彭。調(diào)到dispatchLayoutStep1() 分發(fā)布局步驟1. 這個方法的注釋表示:第一步執(zhí)行以下操作:
1.處理adapter的更新
2.確定要執(zhí)行的動畫
3.保存當前views的信息
4.如果必要,運行可預(yù)測布局尖滚,并保存其信息

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喉刘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子漆弄,更是在濱河造成了極大的恐慌睦裳,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撼唾,死亡現(xiàn)場離奇詭異廉邑,居然都是意外死亡,警方通過查閱死者的電腦和手機倒谷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門蛛蒙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人渤愁,你說我怎么就攤上這事宇驾。” “怎么了猴伶?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵课舍,是天一觀的道長塌西。 經(jīng)常有香客問我,道長筝尾,這世上最難降的妖魔是什么捡需? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮筹淫,結(jié)果婚禮上站辉,老公的妹妹穿的比我還像新娘。我一直安慰自己损姜,他們只是感情好饰剥,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著摧阅,像睡著了一般汰蓉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棒卷,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天顾孽,我揣著相機與錄音,去河邊找鬼比规。 笑死若厚,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蜒什。 我是一名探鬼主播测秸,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼灾常!你這毒婦竟也來了霎冯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤岗憋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锚贱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仔戈,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年拧廊,在試婚紗的時候發(fā)現(xiàn)自己被綠了监徘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡吧碾,死狀恐怖凰盔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情倦春,我是刑警寧澤户敬,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布落剪,位于F島的核電站,受9級特大地震影響尿庐,放射性物質(zhì)發(fā)生泄漏忠怖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一抄瑟、第九天 我趴在偏房一處隱蔽的房頂上張望凡泣。 院中可真熱鬧,春花似錦皮假、人聲如沸鞋拟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贺纲。三九已至,卻和暖如春布轿,著一層夾襖步出監(jiān)牢的瞬間哮笆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工汰扭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留稠肘,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓萝毛,卻偏偏與公主長得像项阴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子笆包,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,072評論 25 707
  • 簡介: 提供一個讓有限的窗口變成一個大數(shù)據(jù)集的靈活視圖环揽。 術(shù)語表: Adapter:RecyclerView的子類...
    酷泡泡閱讀 5,163評論 0 16
  • RecyclerView 源碼分析 本文原創(chuàng),轉(zhuǎn)載請注明出處庵佣。歡迎關(guān)注我的 簡書 歉胶,關(guān)注我的專題 Android ...
    MeloDev閱讀 10,107評論 6 49
  • 這篇文章分三個部分,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法巴粪;工作原理與ListView比...
    LucasAdam閱讀 4,388評論 0 27
  • —10— 不知不覺間通今,月影已移到了東墻上。 暗了下來的炕上老爺鼾聲如雷肛根,我卻突然覺得渾身燥熱難耐辫塌,好像有千萬只螞蟻...
    聽風(fēng)閣主人閱讀 928評論 0 2