RecyclerView 源碼分析(四) - RecyclerView的動(dòng)畫機(jī)制

??距離上一篇RecyclerView源碼分析的文章已經(jīng)過去了10多天,今天我們將來(lái)看看RecyclerView的動(dòng)畫機(jī)制。不過,本文不會(huì)分析ItemAnimator相關(guān)的知識(shí),而是理解RecyclerView怎么執(zhí)行ItemAnimator的,有關(guān)ItemAniamtor的知識(shí),后面我會(huì)寫專門的文章來(lái)分析。
??本文參考資料:

  1. RecyclerView animations - AndroidDevSummit write-up
  2. RecyclerView.ItemAnimator終極解讀(一)--RecyclerView源碼解析

??注意,本文所有的代碼都來(lái)自于27.1.1。

1. 概述

??RecyclerView之所以受歡迎,有一部分的原因得歸功于它的動(dòng)畫機(jī)制。我們可以通過RecyclerViewsetItemAnimator方法來(lái)給每個(gè)Item設(shè)置在不同行為下,執(zhí)行不同的動(dòng)畫,非常的簡(jiǎn)單。盡管我們知道怎么給RecyclerView設(shè)置動(dòng)畫,但是RecyclerView是怎么通過ItemAnimator來(lái)給每個(gè)Item實(shí)現(xiàn)動(dòng)畫,這里面的原理值得我們?nèi)パ芯亢蛯W(xué)習(xí)。
??在正式分析RecyclerView的動(dòng)畫機(jī)制之前,我們先對(duì)幾個(gè)詞語(yǔ)有一個(gè)概念,我們來(lái)看看:

詞語(yǔ) 含義
Disappearance 表示在動(dòng)畫之前,ItemView是可見的,動(dòng)畫之后就可不見了。這里的操作包括,remove操作和普通的滑動(dòng)導(dǎo)致ItemView劃出屏幕
Appearance 表示動(dòng)畫之前,ItemView是不可見,動(dòng)畫之后就可見了。這里的操作包括,add操作和普通的滑動(dòng)導(dǎo)致ItemView劃入屏幕
Persistence 表示動(dòng)畫前后,狀態(tài)是不變的。這里面的操作包括,無(wú)任何操作
change 表示動(dòng)畫前后,狀態(tài)是不變的。這里面的操作包括,change操作。

??還有注意的一點(diǎn)就是,ViewHolder不是用來(lái)記錄ItemView的位置信息,而是進(jìn)行數(shù)據(jù)綁定的,所以在動(dòng)畫中,關(guān)于位置信息的記錄不是依靠ViewHolder來(lái)實(shí)現(xiàn)的,而是依靠一個(gè)叫ItemHolderInfo的類實(shí)現(xiàn)的,在這個(gè)類里面,有四個(gè)成員變量,分別記錄ItemView的left、top、right和bottom四個(gè)位置信息。
??最后還需要注意一點(diǎn)就是,我們從RecyclerView的三大流程中可以得到,在RecyclerView的內(nèi)部,dispatchLayout分為三步,其中dispathchLayoutStep1被稱為預(yù)布局,在這里主要是保存ItemViewOldViewHolder,同時(shí)還會(huì)記錄下每個(gè)ItemView在動(dòng)畫之前的位置信息;與之對(duì)應(yīng)的dispathchLayoutStep3被稱為后布局,主要結(jié)合真正布局和預(yù)布局的相關(guān)信息來(lái)實(shí)現(xiàn)進(jìn)行動(dòng)畫,當(dāng)然前提是RecyclerView本身支持動(dòng)畫。


??本文打算從兩個(gè)角度來(lái)分析RecyclerView的動(dòng)畫,一是從普通三大的流程來(lái)看,這是動(dòng)畫機(jī)制的核心所在;而是從Adapeter的角度上來(lái)看,看看我們每次在調(diào)用Adapter的notify相關(guān)方法之后,是怎么進(jìn)行執(zhí)行動(dòng)畫的(實(shí)際上也是回到三大流程里面)。

1. 再來(lái)看RecyclerView的三大流程

??取這個(gè)題目,我感覺有特別的含義。首先,本次分析動(dòng)畫機(jī)制就是重新來(lái)看看三大流程,當(dāng)然本次分三大流程肯定沒有之前的那么仔細(xì),其次側(cè)重點(diǎn)也不同;其次,本次再來(lái)看RecyclerView的三大流程,還可以填之前在分析RecyclerView的三大流程留下的坑。
??本次的分析重點(diǎn)在于dispathchLayoutStep1dispathchLayoutStep3,不會(huì)分析完整的三大流程,所以,還有不懂RecyclerView三大流程的同學(xué),可以參考我的文章:RecyclerView 源碼分析(一) - RecyclerView的三大流程。
??我們先來(lái)看看dispatchLayoutStep1方法:

    private void 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);
                }
            }
        }
        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();
        } else {
            clearOldPositions();
        }
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
        mState.mLayoutStep = State.STEP_LAYOUT;
    }

??我將dispatchLayoutStep1方法分為2步(實(shí)際上從谷歌爸爸的注釋,我們也可以得出來(lái))。

  1. 找到每個(gè)沒有被remove 掉的ItemView,將它的ViewHolder(OldViewHolder)放在ViewInfoStore里面,同時(shí)還將它預(yù)布局的位置放在ViewInfoStore里面。這兩個(gè)信息在后面做動(dòng)畫時(shí)都會(huì)用到。
  2. 如果當(dāng)前RecyclerViewLayoutManager支持predictive item animations(supportsPredictiveItemAnimations方法返回true,我覺得用英語(yǔ)描述這種動(dòng)畫挺好的,因?yàn)槲也恢涝趺捶g),會(huì)真正的進(jìn)行預(yù)布局。在這一步,會(huì)先調(diào)用LayoutManageronLayoutChildren進(jìn)行一次布局,不過這次布局知識(shí)預(yù)布局,也就是說(shuō)不是真正的布局,只是先確定每個(gè)ItemView的位置。預(yù)布局之后,此時(shí)取到的每個(gè)ItemViewViewHolderItemHolderInfo,便是每個(gè)ItemView的最終信息。

??第二步的信息與第一步的信息相互呼應(yīng),第一步是變化前的信息,第二步是變化后的信息。這些都是為dispatchLayout3階段的動(dòng)畫做準(zhǔn)備。其中,我們發(fā)現(xiàn)相對(duì)于第一步,第二步變得復(fù)雜了很多。不過,我們可以發(fā)現(xiàn),不管怎么復(fù)雜,都是通過調(diào)用addToOldChangeHolders方法來(lái)保存當(dāng)前ItemViewViewHolder(在LayoutManageronLayoutChildren方法前后,在同一個(gè)位置上,不一定是同一個(gè)ItemView,也不一定是同一個(gè)ViewHolder),然后調(diào)用addXXXLayout方法將位置信息(ItemHolderInfo)保存起來(lái)。
??然后,我們?cè)賮?lái)看看dispatchLayoutStep3階段:

    private void dispatchLayoutStep3() {
        mState.assertLayoutStep(State.STEP_ANIMATIONS);
        startInterceptRequestLayout();
        onEnterLayoutOrScroll();
        mState.mLayoutStep = State.STEP_START;
        // 將相關(guān)信息取到,然后添加到ViewInfoStore
        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
            // 觸發(fā)動(dòng)畫
            mViewInfoStore.process(mViewInfoProcessCallback);
        }
        // 清理工作階段
    }

??我將上面的代碼分為3階段。

  1. 獲得相關(guān)的位置信息(ItemHolderInfo),然后通過addToPostLayout方法將位置保存在ViewInfoStore里面。
  2. 調(diào)用ViewInfoStoreprocess方法觸發(fā)動(dòng)畫。
  3. 進(jìn)行相關(guān)的清理工作。

??這里,我們重點(diǎn)關(guān)注前兩步就行了。
??其中第一步非常容易理解,先是獲到當(dāng)前ItemView的位置信息,保存在ViewInfoStore里面。其中,我們?cè)谶@里發(fā)現(xiàn),如果OldViewHolder不為空的話,會(huì)特別處理,為什么會(huì)這樣處理的呢?其實(shí)這里考慮到change操作,因?yàn)閏hange操作會(huì)涉及到兩個(gè)ItemView的動(dòng)畫變化,所以,我們發(fā)現(xiàn),如果一個(gè)ItemView調(diào)用的是animateChange方法進(jìn)行動(dòng)畫開始,而不是走通用的邏輯(將位置信息通過addToPostLayout方法保存起來(lái),然后調(diào)用process方法進(jìn)行統(tǒng)一的調(diào)用)。
??然后就是第二步。我們來(lái)看看ViewInfoStoreprocess方法,不過在我們?cè)谶@方法之前,我們我們先看看ProcessCallback接口的幾個(gè)方法。

方法 作用
processDisappeared 一個(gè)ItemView從可見到不可見會(huì)回調(diào)這個(gè)方法,主要是執(zhí)行這種情況下的動(dòng)畫
processAppeared 一個(gè)ItemView從不可見到可見會(huì)回調(diào)這個(gè)方法。
processPersistent 一個(gè)ItemView動(dòng)畫前后狀態(tài)為改變,這里面包括:本身未發(fā)生任何操作的ItemView、change操作的ItemView
unused 一個(gè)ItemView的變化不支持動(dòng)畫會(huì)回調(diào)此方法,這里包括比如一個(gè)ItemView先是Appeared然后disappeared,這種情況RecyclerView找不到合適的動(dòng)畫;還有當(dāng)前ItemView缺少preInfo,也就是在預(yù)布局未記錄位置信息,也會(huì)調(diào)用此方法,這種情況經(jīng)常是ItemView進(jìn)行remove操作,但是Adapter調(diào)用的是notifyDataSetChanged方法

??現(xiàn)在,我們正式的來(lái)看看process方法:

    void process(ProcessCallback callback) {
        for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
            final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
            final InfoRecord record = mLayoutHolderMap.removeAt(index);
            if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
                // Appeared then disappeared. Not useful for animations.
                callback.unused(viewHolder);
            } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
                // Set as "disappeared" by the LayoutManager (addDisappearingView)
                if (record.preInfo == null) {
                    // similar to appear disappear but happened between different layout passes.
                    // this can happen when the layout manager is using auto-measure
                    callback.unused(viewHolder);
                } else {
                    callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
                }
            } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
                // Appeared in the layout but not in the adapter (e.g. entered the viewport)
                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
                // Persistent in both passes. Animate persistence
                callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_PRE) != 0) {
                // Was in pre-layout, never been added to post layout
                callback.processDisappeared(viewHolder, record.preInfo, null);
            } else if ((record.flags & FLAG_POST) != 0) {
                // Was not in pre-layout, been added to post layout
                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_APPEAR) != 0) {
                // Scrap view. RecyclerView will handle removing/recycling this.
            } else if (DEBUG) {
                throw new IllegalStateException("record without any reasonable flag combination:/");
            }
            InfoRecord.recycle(record);
        }
    }

??其實(shí)process方法非常簡(jiǎn)單,就是通過相關(guān)的flag來(lái)調(diào)用ProcessCallback相關(guān)的方法。我們現(xiàn)在來(lái)同一個(gè)看看ProcessCallback的每個(gè)方法都怎么實(shí)現(xiàn)的。

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

                @Override
                public void processPersistent(ViewHolder viewHolder,
                        @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
                    viewHolder.setIsRecyclable(false);
                    if (mDataSetHasChangedAfterLayout) {
                        // since it was rebound, use change instead as we'll be mapping them from
                        // stable ids. If stable ids were false, we would not be running any
                        // animations
                        if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
                                postInfo)) {
                            postAnimationRunner();
                        }
                    } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
                        postAnimationRunner();
                    }
                }
                @Override
                public void unused(ViewHolder viewHolder) {
                    mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
                }
            };

??其實(shí)說(shuō)到底,就是調(diào)用了animateXXX方法來(lái)實(shí)現(xiàn),而animateXXX方法里面做了啥?其實(shí)沒啥,就是調(diào)用了ViewCompatpostOnAnimation方法往任務(wù)隊(duì)列后面post一個(gè)Runnable。代碼如下:

    void postAnimationRunner() {
        if (!mPostedAnimatorRunner && mIsAttached) {
            ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
            mPostedAnimatorRunner = true;
        }
    }

??其中,上面的代碼中,我們需要注意的是,postAnimationRunner每次只會(huì)被調(diào)用一次。那么如果在某一次操作中,會(huì)執(zhí)行多個(gè)動(dòng)畫,怎么辦呢?ProcessCallback每個(gè)回調(diào)方法都會(huì)調(diào)用animateXXX方法,而animateXXX方法會(huì)調(diào)用ItemAnimator對(duì)應(yīng)的方法,在ItemAnimator里面,會(huì)將當(dāng)前動(dòng)畫添加到一個(gè)數(shù)組里面,然后通過mItemAnimatorRunner調(diào)用ItemAnimatorrunPendingAnimations方法,runPendingAnimations方法就是所有動(dòng)畫開始的起點(diǎn)。這里,我們就不討論ItemAnimator內(nèi)部的實(shí)現(xiàn),后面有專門的文章來(lái)分析它。

2. 從Adapter角度來(lái)看動(dòng)畫執(zhí)行的機(jī)制

??我們知道,調(diào)用Adapter的notifyDataSetChanged方法,RecyclerView是不會(huì)執(zhí)行動(dòng)畫的;而調(diào)用notifyItemRemoved之類的方法是有動(dòng)畫,這里我們從Adapter的角度來(lái)分析動(dòng)畫。跟ItemAnimator一樣,這里我們也不會(huì)去分析Adapter,后面會(huì)有專門的文章分析它。
??在分析Adapter之前,我們先來(lái)看一個(gè)東西,就是RecyclerViewAdapter怎么進(jìn)行通信。

(1).通過觀察者模式來(lái)實(shí)現(xiàn)RecyclerView 和Adapter的通信

??我們思考這個(gè)問題之前,首先應(yīng)該排除AddapterRecyclerView是強(qiáng)耦合的,也就是說(shuō),Adapter內(nèi)部持有一個(gè)RecyclerView對(duì)象。RecyclerView本身就是插拔式設(shè)計(jì),如果AdapterRecyclerView是強(qiáng)耦合,就違背了插拔式的設(shè)計(jì)思想。那么它倆究竟是怎么進(jìn)行通信的呢?答案已經(jīng)非常的明顯了,兩者是通過觀察者模式來(lái)進(jìn)行通信。
??這其中,Adapter作為被觀察者,RecyclerView作為觀察者,當(dāng)Adapter的數(shù)據(jù)發(fā)生改變時(shí),會(huì)通知它的每個(gè)觀察者。
??RecyclerView本身設(shè)計(jì)又比較特殊,RecyclerView沒有去實(shí)現(xiàn)Observer(這里暫且這么稱呼)接口,而是內(nèi)部持有一個(gè)Observer(RecyclerViewDataObserver)對(duì)象,進(jìn)而監(jiān)聽Adapter的狀態(tài)變化;當(dāng)然Adapter也是如此,并沒有去實(shí)現(xiàn)Observable接口,也是在內(nèi)部持有一個(gè)Observable(AdapterDataObservable)對(duì)象。
??我們來(lái)看Adapternotify方法跟Observer的方法是怎么進(jìn)行對(duì)應(yīng)的。

Adapter的notify方法 與之對(duì)應(yīng)的Observer的方法
notifyItemRemoved notifyItemRangeRemoved
notifyItemChanged notifyItemRangeChanged
notifyItemInserted notifyItemRangeInserted
notifyItemMoved notifyItemMoved

??調(diào)用到Observer的方法時(shí),Observer會(huì)調(diào)用AdapterHelper相關(guān)的方法,在AdapterHelper內(nèi)部會(huì)為每個(gè)操作創(chuàng)建一個(gè)UpdateOp對(duì)象,并且添加到一個(gè)PendingUpdate數(shù)組。我們來(lái)看看相關(guān)代碼(以add為例):

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

??如果onItemRangeInserted返回為true,就調(diào)用triggerUpdateProcessor方法。為什么這里需要判斷是否調(diào)用triggerUpdateProcessor方法,其實(shí)是為了避免多次調(diào)用,比如一個(gè)操作,可能會(huì)導(dǎo)致多種動(dòng)畫執(zhí)行,所以這里保證triggerUpdateProcessor方法只會(huì)被調(diào)用一次。
??然后,我們來(lái)看看triggerUpdateProcessor方法:

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

??其實(shí)不管是if的執(zhí)行語(yǔ)句,還是else里面,最終還是調(diào)用了requestLayout方法,重新走一遍三大流程。
??可見而知,RecyclerView的三大流程到底多么重要。這次,我們看三大流程中的dispatchLayoutStep2方法。我們知道,在Observer階段,每個(gè)操作其實(shí)都創(chuàng)建了一個(gè)UpdateOp對(duì)象,添加到PendingUpdate數(shù)組。那么數(shù)組里面的操作都是什么時(shí)候執(zhí)行的呢?其實(shí)就是在dispatchLayoutStep2方法階段:

    private void dispatchLayoutStep2() {
        // ······
        mAdapterHelper.consumeUpdatesInOnePass();
        // ······
    }

??真正執(zhí)行PendingUpdate的操作是在AdapterHelperconsumeUpdatesInOnePass方法里面,我們來(lái)瞧瞧:

    void consumeUpdatesInOnePass() {
        // we still consume postponed updates (if there is) in case there was a pre-process call
        // w/o a matching consumePostponedUpdates.
        consumePostponedUpdates();
        final int count = mPendingUpdates.size();
        for (int i = 0; i < count; i++) {
            UpdateOp op = mPendingUpdates.get(i);
            switch (op.cmd) {
                case UpdateOp.ADD:
                    mCallback.onDispatchSecondPass(op);
                    mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
                    break;
                case UpdateOp.REMOVE:
                    mCallback.onDispatchSecondPass(op);
                    mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
                    break;
                case UpdateOp.UPDATE:
                    mCallback.onDispatchSecondPass(op);
                    mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
                    break;
                case UpdateOp.MOVE:
                    mCallback.onDispatchSecondPass(op);
                    mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
                    break;
            }
            if (mOnItemProcessedCallback != null) {
                mOnItemProcessedCallback.run();
            }
        }
        recycleUpdateOpsAndClearList(mPendingUpdates);
        mExistingUpdateTypes = 0;
    }

??雖然代碼不少,但是我們發(fā)現(xiàn)了,最終的操作都是調(diào)用到了Callback接口里面了。而Callback做了什么呢?主要是做了兩件事:

  1. 可能會(huì)更新一些ViewHolder的position
  2. 會(huì)更新一些ViewHolder的flag,比如說(shuō),remove的flag或者update的flag。

??這部分的內(nèi)容,我們后面分析Adapter會(huì)詳細(xì)的分析,本文就不做過多的介紹了。
??到這里,每個(gè)ViewHolder的position都更新完畢,并且每個(gè)ViewHolder的flag也已經(jīng)更新完畢。這樣,到了dispatchLayoutStep3階段,就知道每個(gè)ViewHolder應(yīng)該做什么動(dòng)畫。
??然后,我們來(lái)看看為什么調(diào)用AdapternotifyDataSetChanged方法不執(zhí)行動(dòng)畫呢?

(2). 為什么notifyDataSetChanged方法不會(huì)執(zhí)行動(dòng)畫呢?

??notifyDataSetChanged方法會(huì)回調(diào)到ObservernotifyChanged方法里面,我們看看notifyChanged方法干什么:

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

            processDataSetCompletelyChanged(true);
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();
            }
        }

??在這個(gè)方法里面,我們需要特別關(guān)注processDataSetCompletelyChanged方法。我們來(lái)看看:

    void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
        mDispatchItemsChangedEvent |= dispatchItemsChanged;
        mDataSetHasChangedAfterLayout = true;
        markKnownViewsInvalid();
    }

??在processDataSetCompletelyChanged方法里面,調(diào)用了markKnownViewsInvalid方法所有的ViewHolder標(biāo)記為了FLAG_INVALID。這個(gè)操作直接導(dǎo)致了,我們?cè)陬A(yù)布局階段不能正確獲得每個(gè)ItemView的位置信息和OldViewHolder,進(jìn)而導(dǎo)致在后布局階段不能執(zhí)行動(dòng)畫。這就是notifyDataSetChanged方法為什么不執(zhí)行動(dòng)畫的原因。

3. 總結(jié)

??RecyclerView的動(dòng)畫機(jī)制還是比較簡(jiǎn)單的,這里我們對(duì)它做一個(gè)簡(jiǎn)單的總結(jié)。

  1. RecyclerView執(zhí)行動(dòng)畫的機(jī)制在于,在預(yù)布局階段將每個(gè)ItemView的位置信息和ViewHolder保存起來(lái),在后布局階段,根據(jù)每個(gè)ItemViewViewHolderflag狀態(tài)來(lái)判斷執(zhí)行什么動(dòng)畫,根據(jù)位置信息來(lái)判斷怎么做動(dòng)畫。
  2. Adapter的notify方法之所以能夠執(zhí)行動(dòng)畫,是因?yàn)樗麄冊(cè)谌罅鞒讨薪o每個(gè)ViewHolder打了響應(yīng)的flag,包括remove的flag或者update的flag等。而在后布局中,正是根據(jù)flag來(lái)執(zhí)行不同的動(dòng)畫的。
  3. notifyDataSetChanged方法之所以不支持動(dòng)畫,那是因?yàn)?code>notifyDataSetChanged方法會(huì)使每個(gè)ViewHolder失效(打了FLAG_INVALID標(biāo)記),所以導(dǎo)致在預(yù)布局階段,不能正確的獲得每個(gè)ItemView的位置信息和ViewHolder,進(jìn)而導(dǎo)致動(dòng)畫不能執(zhí)行。

??如果不出意外的話,下一篇文章將分析Adapter

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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