RecyclerView 源碼分析(四) - RecyclerView的動畫機制

  距離上一篇RecyclerView源碼分析的文章已通過去了10多天,今天咱們未來看看RecyclerView的動畫機制。不過,本文不會分析ItemAnimator相關的知識,而是理解RecyclerView怎麼執行ItemAnimator的,有關ItemAniamtor的知識,後面我會寫專門的文章來分析。android

  本文參考資料:git

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

  注意,本文全部的代碼都來自於27.1.1。github

1. 概述

  RecyclerView之因此受歡迎,有一部分的緣由得歸功於它的動畫機制。咱們能夠經過RecyclerViewsetItemAnimator方法來給每一個Item設置在不一樣行爲下,執行不一樣的動畫,很是的簡單。儘管咱們知道怎麼給RecyclerView設置動畫,可是RecyclerView是怎麼經過ItemAnimator來給每一個Item實現動畫,這裏面的原理值得咱們去研究和學習。數組

  在正式分析RecyclerView的動畫機制以前,咱們先對幾個詞語有一個概念,咱們來看看:bash

詞語 含義
Disappearance 表示在動畫以前,ItemView是可見的,動畫以後就可不見了。這裏的操做包括,remove操做和普通的滑動致使ItemView劃出屏幕
Appearance 表示動畫以前,ItemView是不可見,動畫以後就可見了。這裏的操做包括,add操做和普通的滑動致使ItemView劃入屏幕
Persistence 表示動畫先後,狀態是不變的。這裏面的操做包括,無任何操做
change 表示動畫先後,狀態是不變的。這裏面的操做包括,change操做。

  還有注意的一點就是,ViewHolder不是用來記錄ItemView的位置信息,而是進行數據綁定的,因此在動畫中,關於位置信息的記錄不是依靠ViewHolder來實現的,而是依靠一個叫ItemHolderInfo的類實現的,在這個類裏面,有四個成員變量,分別記錄ItemView的left、top、right和bottom四個位置信息。app

  最後還須要注意一點就是,咱們從RecyclerView的三大流程中能夠獲得,在RecyclerView的內部,dispatchLayout分爲三步,其中dispathchLayoutStep1被稱爲預佈局,在這裏主要是保存ItemViewOldViewHolder,同時還會記錄下每一個ItemView在動畫以前的位置信息;與之對應的dispathchLayoutStep3被稱爲後佈局,主要結合真正佈局和預佈局的相關信息來實現進行動畫,固然前提是RecyclerView自己支持動畫。less


  本文打算從兩個角度來分析RecyclerView的動畫,一是從普通三大的流程來看,這是動畫機制的核心所在;而是從Adapeter的角度上來看,看看咱們每次在調用Adapter的notify相關方法以後,是怎麼進行執行動畫的(實際上也是回到三大流程裏面)。 #1. 再來看RecyclerView的三大流程   取這個題目,我感受有特別的含義。首先,本次分析動畫機制就是從新來看看三大流程,固然本次分三大流程確定沒有以前的那麼仔細,其次側重點也不一樣;其次,本次再來看RecyclerView的三大流程,還能夠填以前在分析RecyclerView的三大流程留下的坑。ide

  本次的分析重點在於dispathchLayoutStep1dispathchLayoutStep3,不會分析完整的三大流程,因此,還有不懂RecyclerView三大流程的同窗,能夠參考個人文章:RecyclerView 源碼分析(一) - RecyclerView的三大流程oop

  咱們先來看看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步(實際上從谷歌爸爸的註釋,咱們也能夠得出來)。

  1. 找到每一個沒有被remove 掉的ItemView,將它的ViewHolder(OldViewHolder)放在ViewInfoStore裏面,同時還將它預佈局的位置放在ViewInfoStore裏面。這兩個信息在後面作動畫時都會用到。
  2. 若是當前RecyclerViewLayoutManager支持predictive item animations(supportsPredictiveItemAnimations方法返回true,我以爲用英語描述這種動畫挺好的,由於我不知道怎麼翻譯),會真正的進行預佈局。在這一步,會先調用LayoutManageronLayoutChildren進行一次佈局,不過此次佈局知識預佈局,也就是說不是真正的佈局,只是先肯定每一個ItemView的位置。預佈局以後,此時取到的每一個ItemViewViewHolderItemHolderInfo,即是每一個ItemView的最終信息。

  第二步的信息與第一步的信息相互呼應,第一步是變化前的信息,第二步是變化後的信息。這些都是爲dispatchLayout3階段的動畫作準備。其中,咱們發現相對於第一步,第二步變得複雜了不少。不過,咱們能夠發現,無論怎麼複雜,都是經過調用addToOldChangeHolders方法來保存當前ItemViewViewHolder(在LayoutManageronLayoutChildren方法先後,在同一個位置上,不必定是同一個ItemView,也不必定是同一個ViewHolder),而後調用addXXXLayout方法將位置信息(ItemHolderInfo)保存起來。

  而後,咱們再來看看dispatchLayoutStep3階段:

private void dispatchLayoutStep3() {
        mState.assertLayoutStep(State.STEP_ANIMATIONS);
        startInterceptRequestLayout();
        onEnterLayoutOrScroll();
        mState.mLayoutStep = State.STEP_START;
        // 將相關信息取到,而後添加到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
            // 觸發動畫
            mViewInfoStore.process(mViewInfoProcessCallback);
        }
        // 清理工做階段
    }
複製代碼

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

  1. 得到相關的位置信息(ItemHolderInfo),而後經過addToPostLayout方法將位置保存在ViewInfoStore裏面。
  2. 調用ViewInfoStoreprocess方法觸發動畫。
  3. 進行相關的清理工做。

  這裏,咱們重點關注前兩步就好了。

  其中第一步很是容易理解,先是獲到當前ItemView的位置信息,保存在ViewInfoStore裏面。其中,咱們在這裏發現,若是OldViewHolder不爲空的話,會特別處理,爲何會這樣處理的呢?其實這裏考慮到change操做,由於change操做會涉及到兩個ItemView的動畫變化,因此,咱們發現,若是一個ItemView調用的是animateChange方法進行動畫開始,而不是走通用的邏輯(將位置信息經過addToPostLayout方法保存起來,而後調用process方法進行統一的調用)。

  而後就是第二步。咱們來看看ViewInfoStoreprocess方法,不過在咱們在這方法以前,咱們咱們先看看ProcessCallback接口的幾個方法。

方法 做用
processDisappeared 一個ItemView從可見到不可見會回調這個方法,主要是執行這種狀況下的動畫
processAppeared 一個ItemView從不可見到可見會回調這個方法。
processPersistent 一個ItemView動畫先後狀態爲改變,這裏麪包括:自己未發生任何操做的ItemView、change操做的ItemView
unused 一個ItemView的變化不支持動畫會回調此方法,這裏包括好比一個ItemView先是Appeared而後disappeared,這種狀況RecyclerView找不到合適的動畫;還有當前ItemView缺乏preInfo,也就是在預佈局未記錄位置信息,也會調用此方法,這種狀況常常是ItemView進行remove操做,可是Adapter調用的是notifyDataSetChanged方法

  如今,咱們正式的來看看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);
        }
    }
複製代碼

  其實process方法很是簡單,就是經過相關的flag來調用ProcessCallback相關的方法。咱們如今來同一個看看ProcessCallback的每一個方法都怎麼實現的。

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); } }; 複製代碼

  其實說到底,就是調用了animateXXX方法來實現,而animateXXX方法裏面作了啥?其實沒啥,就是調用了ViewCompatpostOnAnimation方法往任務隊列後面post一個Runnable。代碼以下:

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

  其中,上面的代碼中,咱們須要注意的是,postAnimationRunner每次只會被調用一次。那麼若是在某一次操做中,會執行多個動畫,怎麼辦呢?ProcessCallback每一個回調方法都會調用animateXXX方法,而animateXXX方法會調用ItemAnimator對應的方法,在ItemAnimator裏面,會將當前動畫添加到一個數組裏面,而後經過mItemAnimatorRunner調用ItemAnimatorrunPendingAnimations方法,runPendingAnimations方法就是全部動畫開始的起點。這裏,咱們就不討論ItemAnimator內部的實現,後面有專門的文章來分析它。

2. 從Adapter角度來看動畫執行的機制

  咱們知道,調用Adapter的notifyDataSetChanged方法,RecyclerView是不會執行動畫的;而調用notifyItemRemoved之類的方法是有動畫,這裏咱們從Adapter的角度來分析動畫。跟ItemAnimator同樣,這裏咱們也不會去分析Adapter,後面會有專門的文章分析它。

  在分析Adapter以前,咱們先來看一個東西,就是RecyclerViewAdapter怎麼進行通訊。

(1).經過觀察者模式來實現RecyclerView 和Adapter的通訊

  咱們思考這個問題以前,首先應該排除AddapterRecyclerView是強耦合的,也就是說,Adapter內部持有一個RecyclerView對象。RecyclerView自己就是插拔式設計,若是AdapterRecyclerView是強耦合,就違背了插拔式的設計思想。那麼它倆到底是怎麼進行通訊的呢?答案已經很是的明顯了,二者是經過觀察者模式來進行通訊。

  這其中,Adapter做爲被觀察者,RecyclerView做爲觀察者,當Adapter的數據發生改變時,會通知它的每一個觀察者。

  RecyclerView自己設計又比較特殊,RecyclerView沒有去實現Observer(這裏暫且這麼稱呼)接口,而是內部持有一個Observer(RecyclerViewDataObserver)對象,進而監聽Adapter的狀態變化;固然Adapter也是如此,並無去實現Observable接口,也是在內部持有一個Observable(AdapterDataObservable)對象。

  咱們來看Adapternotify方法跟Observer的方法是怎麼進行對應的。

Adapter的notify方法 與之對應的Observer的方法
notifyItemRemoved notifyItemRangeRemoved
notifyItemChanged notifyItemRangeChanged
notifyItemInserted notifyItemRangeInserted
notifyItemMoved notifyItemMoved

  調用到Observer的方法時,Observer會調用AdapterHelper相關的方法,在AdapterHelper內部會爲每一個操做建立一個UpdateOp對象,而且添加到一個PendingUpdate數組。咱們來看看相關代碼(以add爲例):

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

  若是onItemRangeInserted返回爲true,就調用triggerUpdateProcessor方法。爲何這裏須要判斷是否調用triggerUpdateProcessor方法,實際上是爲了不屢次調用,好比一個操做,可能會致使多種動畫執行,因此這裏保證triggerUpdateProcessor方法只會被調用一次。

  而後,咱們來看看triggerUpdateProcessor方法:

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

  其實不論是if的執行語句,仍是else裏面,最終仍是調用了requestLayout方法,從新走一遍三大流程。

  可見而知,RecyclerView的三大流程到底多麼重要。此次,咱們看三大流程中的dispatchLayoutStep2方法。咱們知道,在Observer階段,每一個操做其實都建立了一個UpdateOp對象,添加到PendingUpdate數組。那麼數組裏面的操做都是何時執行的呢?其實就是在dispatchLayoutStep2方法階段:

private void dispatchLayoutStep2() {
        // ······
        mAdapterHelper.consumeUpdatesInOnePass();
        // ······
    }
複製代碼

  真正執行PendingUpdate的操做是在AdapterHelperconsumeUpdatesInOnePass方法裏面,咱們來瞧瞧:

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;
    }
複製代碼

  雖然代碼很多,可是咱們發現了,最終的操做都是調用到了Callback接口裏面了。而Callback作了什麼呢?主要是作了兩件事:

  1. 可能會更新一些ViewHolder的position
  2. 會更新一些ViewHolder的flag,好比說,remove的flag或者update的flag。

  這部分的內容,咱們後面分析Adapter會詳細的分析,本文就不作過多的介紹了。

  到這裏,每一個ViewHolder的position都更新完畢,而且每一個ViewHolder的flag也已經更新完畢。這樣,到了dispatchLayoutStep3階段,就知道每一個ViewHolder應該作什麼動畫。

  而後,咱們來看看爲何調用AdapternotifyDataSetChanged方法不執行動畫呢?

(2). 爲何notifyDataSetChanged方法不會執行動畫呢?

  notifyDataSetChanged方法會回調到ObservernotifyChanged方法裏面,咱們看看notifyChanged方法幹什麼:

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

            processDataSetCompletelyChanged(true);
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();
            }
        }
複製代碼

  在這個方法裏面,咱們須要特別關注processDataSetCompletelyChanged方法。咱們來看看:

void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
        mDispatchItemsChangedEvent |= dispatchItemsChanged;
        mDataSetHasChangedAfterLayout = true;
        markKnownViewsInvalid();
    }
複製代碼

  在processDataSetCompletelyChanged方法裏面,調用了markKnownViewsInvalid方法全部的ViewHolder標記爲了FLAG_INVALID。這個操做直接致使了,咱們在預佈局階段不能正確得到每一個ItemView的位置信息和OldViewHolder,進而致使在後佈局階段不能執行動畫。這就是notifyDataSetChanged方法爲何不執行動畫的緣由。

3. 總結

  RecyclerView的動畫機制仍是比較簡單的,這裏咱們對它作一個簡單的總結。

  1. RecyclerView執行動畫的機制在於,在預佈局階段將每一個ItemView的位置信息和ViewHolder保存起來,在後佈局階段,根據每一個ItemViewViewHolderflag狀態來判斷執行什麼動畫,根據位置信息來判斷怎麼作動畫。
  2. Adapter的notify方法之因此可以執行動畫,是由於他們在三大流程中給每一個ViewHolder打了響應的flag,包括remove的flag或者update的flag等。而在後佈局中,正是根據flag來執行不一樣的動畫的。
  3. notifyDataSetChanged方法之因此不支持動畫,那是由於notifyDataSetChanged方法會使每一個ViewHolder失效(打了FLAG_INVALID標記),因此致使在預佈局階段,不能正確的得到每一個ItemView的位置信息和ViewHolder,進而致使動畫不能執行。

  若是不出意外的話,下一篇文章將分析Adapter

相關文章
相關標籤/搜索