RecyclerView 源碼分析(八) - ItemAnimator的源碼分析(源碼分析系列終篇)

  ItemAnimator做爲RecyclerView的主要組成部分之一,其重要性天然不可言喻。由於ItemAnimator的存在,因此出現了不少精彩紛呈的動畫,這使得RecyclerView更加惹人喜好。所以,學習ItemAnimator的源碼是勢在必行的,由於咱們瞭解原理以後,就能夠自定義動畫了,再也不受人束縛。git

  本文參考資料:github

  1. recyclerview-animators
  2. RecyclerView 源碼分析(四) - RecyclerView的動畫機制

  本文打算才以下幾個部分來分析ItemAnimator的源碼:數組

  1. ItemAnimator類相關方法的分析和總結。
  2. SimpleItemAnimator相關代碼的分析。
  3. DefaultItemAnimator相關代碼的分析。
  4. 自定義一個ItemAnimator

1. 概述

  首先咱們來對ItemAnimator的整個結構作一個簡單的概述,方便你們理解。bash

  一般來講,自定義一個ItemAnimator的過程是:ItemAnimator ->SimpleItemAnimator -> 自定義ItemAnimator。包括官方提供的DefaultItemAnimator也是這樣來定義的,那這樣來定義有好處的呢?這樣定義,結構層次比較清晰,咱們自定義ItemAnimator比較方便,只須要關注動畫怎麼實現就行。咱們來看看這三層分別幹了什麼:架構

結構層次 做用
ItemAnimator 定義ItemAnimator的模板,自定義這裏包括4個抽象方法,也是很是重要的抽象方法,分別是:animateDisappearanceanimateAppearanceanimatePersistenceanimateChange
SimpleItemAnimator 實現了四個抽象方法,根據調用4個抽象方法的時機不一樣,因此會作不一樣的動畫,因此又對外提供了4個抽象方法,分別是:animateRemoveanimateAddanimateMoveanimateChange,分別對應刪除、添加、移動和改變的動畫。
自定義ItemAnimator 主要實現4種操做的動畫,也包括結束動畫相關實現

  而咱們在自定義ItemAnimator時,只須要考慮第三層就OK,上面兩層的邏輯谷歌爸爸已經幫咱們實現了。自定義過ItemAnimator的同窗應該都知道,儘管只關注第三層,可是實現仍是那麼麻煩,介於這個問題,我會提出一個很是簡單的自定義itemAnimator的方案。app

2. ItemAnimator

  咱們從上往下,看看每一層都爲咱們作了哪些事情,首先咱們來了解一下ItemAnimatorItemAnimator總的來講比較簡單,咱們來看看ItemAnimator幾個方法:ide

方法名 做用
animateDisappearance 抽象方法,供第二層實現。此方法的調用時機是ItemView從當前的可見區域消失,其中包括:1.ItemView執行了remove操做;2. ItemView執行move操做移動到不可見區域。在此方法裏面,根據不一樣的狀況,執行move動畫或者執行remove動畫
animateAppearance 抽象方法,供第二層實現。此方法的調用時機是ItemView出如今可見區域,其中包括:1. ItemView執行了add操做;2. ItemView執行move操做從不可見區域移動到可見區域。在此方法裏面,根據不一樣的狀況,執行add動畫或者執行move動畫。
animatePersistence 抽象方法,供第二層實現。此方法的調用時機是ItemView未進行任何操做。在此方法裏面,根據不一樣的狀況,會執行remove動畫(好比說當前ItemView上面有一個ItemView執行了reomve操做)或者無任何動畫。
animateChange 抽象方法,供第二層實現。此方法的調用時機是ItemView進行了change操做。在方法裏面,會執行change動畫。

  在ItemAnimator中,上面4個方法很是的重要,RecyclerView就是經過這四個方法來給每一個ItemView添加不一樣的動畫。在這一層,咱們須要掌握的就是,記住這4個方法的調用時機,這樣咱們在看SimpleItemAnimator代碼時,纔不會不知所措。源碼分析

3. SimpleItemAnimator

  就像在上面概述所說的同樣,SimpleItemAnimator處於第二層,負責實現ItemAnimator的4個抽象方法,而後又提供了四種操做須要分別調用的抽象方法,這樣作就更加細化了動畫執行的狀況,簡化了自定義ItemAnimator的過程。post

  在正式看SimpleItemAnimator的源碼以前,咱們先來看看SimpleItemAnimator幾個方法的介紹。學習

方法名 參數 說明
animateRemove ViewHolder 噹噹前的ItemView執行了remove操做須要執行remove動畫時,會回調此方法。
animateAdd ViewHolder 噹噹前的ItemView執行了add操做須要執行add動畫時,會回調此方法。
animateMove ViewHolder 噹噹前的ItemView執行了move操做,或者它以前有ItemView執行了remove操做或者add操做,會回調此方法。
animateChange ViewHolder oldHolder,ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop 噹噹前的ItemView執行了change操做,會調用此方法。

  咱們看到到了SimpleItemAnimator這一層,每一個ItemView應該作什麼動畫,如今已經一清二楚了,再也不像第一層裏面那樣,一個方法裏面可能涉及到多種狀況,每種狀況可能執行不一樣的動畫。   如今咱們分別來看看SimpleItemAnimatorItemAnimator的4個抽象方法的實現,看看他是怎麼來判斷一個ItemView執行何種動畫的。

  首先,咱們來看一下animateDisappearance方法:

@Override
    public boolean animateDisappearance(@NonNull ViewHolder viewHolder,
            @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
        int oldLeft = preLayoutInfo.left;
        int oldTop = preLayoutInfo.top;
        View disappearingItemView = viewHolder.itemView;
        int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
        int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
        if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
            disappearingItemView.layout(newLeft, newTop,
                    newLeft + disappearingItemView.getWidth(),
                    newTop + disappearingItemView.getHeight());
            if (DEBUG) {
                Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView);
            }
            return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop);
        } else {
            if (DEBUG) {
                Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView);
            }
            return animateRemove(viewHolder);
        }
    }
複製代碼

  animateDisappearance方法表達的意思很是簡單,首先判斷當前ItemView是否須要執行的是move動畫,若是是,那麼就調用animateMove方法;若是不是的話,那麼就調用animateRemove方法用來執行remove動畫。

  在這個方法裏面,remove操做咱們理解,可是move操做是什麼意思呢?首先咱們得搞清楚animateDisappearance方法的調用時機,animateDisappearance方法表示在ItemView從可見狀態變爲不可見狀態,這裏包括:remove操做和ItemView從可見區域移動到不可見區域。因此在animateDisappearance方法裏面,執行move動畫並不意外。

  而後,咱們來看一下animateAppearance方法:

@Override
    public boolean animateAppearance(@NonNull ViewHolder viewHolder,
            @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
        if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left
                || preLayoutInfo.top != postLayoutInfo.top)) {
            // slide items in if before/after locations differ
            if (DEBUG) {
                Log.d(TAG, "APPEARING: " + viewHolder + " with view " + viewHolder);
            }
            return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top,
                    postLayoutInfo.left, postLayoutInfo.top);
        } else {
            if (DEBUG) {
                Log.d(TAG, "ADDED: " + viewHolder + " with view " + viewHolder);
            }
            return animateAdd(viewHolder);
        }
    }

複製代碼

  animateAppearance方法表示在ItemView從不可見狀態變爲可見狀態,因此這裏包括add操做和move操做。move操做表示的意思跟animateDisappearance方法的差很少。

  而後,咱們再來看看animatePersistence方法:

public boolean animatePersistence(@NonNull ViewHolder viewHolder,
            @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
        if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
            if (DEBUG) {
                Log.d(TAG, "PERSISTENT: " + viewHolder
                        + " with view " + viewHolder.itemView);
            }
            return animateMove(viewHolder,
                    preInfo.left, preInfo.top, postInfo.left, postInfo.top);
        }
        dispatchMoveFinished(viewHolder);
        return false;
    }
複製代碼

  animatePersistence方法比其餘方法都簡單,這裏只進行了move動畫。固然若是不執行任何動畫,這裏會返回false,而且會調用dispatchMoveFinished方法,這是基本要求,當每一個動畫執行完畢以後,都是調用相關方法來通知動畫執行結束了。

  最後,咱們再來看看animateChange方法:

@Override
    public boolean animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
            @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
        if (DEBUG) {
            Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView);
        }
        final int fromLeft = preInfo.left;
        final int fromTop = preInfo.top;
        final int toLeft, toTop;
        if (newHolder.shouldIgnore()) {
            toLeft = preInfo.left;
            toTop = preInfo.top;
        } else {
            toLeft = postInfo.left;
            toTop = postInfo.top;
        }
        return animateChange(oldHolder, newHolder, fromLeft, fromTop, toLeft, toTop);
    }
複製代碼

  animateChange方法更加的簡單,最後始終調用了animateChange抽象方法。

4. DefaultItemAnimator

  咱們在看DefaultItemAnimator的源碼以前,先來看看它有哪些成員變量:

private static TimeInterpolator sDefaultInterpolator;

    private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
    private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>();
    private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
    private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();

    ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>();
    ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
    ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();

    ArrayList<ViewHolder> mAddAnimations = new ArrayList<>();
    ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>();
    ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
    ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>();
複製代碼

  在DefaultItemAnimator的內部,成員變量主要分爲4個部分。這其中,sDefaultInterpolator就是給ItemView設置的一個動畫插值器對象,在DefaultItemAnimator內部,這個插值器是AccelerateDecelerateInterpolator插值器;而後第二部分mPendingxxx數組,這部分就是用來存儲每一個ItemView須要作動畫的相關信息,例如,move動畫就須要移動的起始位置和終點位置,這部分的數組纔是作動畫的真正作動畫須要的;第三部分和第四部分是mXXXListmXXXAnimations,一般來講,都是用於結束動畫的,本文後面會簡單的分析他們。

  簡單的瞭解這四部分的成員變量以後,如今咱們重點看一下四個抽象方法的實現。

(1). animateRemove

@Override
    public boolean animateRemove(final ViewHolder holder) {
        resetAnimation(holder);
        mPendingRemovals.add(holder);
        return true;
    }
複製代碼

  animateRemove方法的實現很是簡單,就是往mPendingRemovals數組裏面添加一個元素。

(2). animateAdd

  而後,咱們再來看一下animateAdd方法:

@Override
    public boolean animateAdd(final ViewHolder holder) {
        resetAnimation(holder);
        holder.itemView.setAlpha(0);
        mPendingAdditions.add(holder);
        return true;
    }
複製代碼

  animateAdd方法的實現也是很是簡單。這裏咱們看到有一個操做就是holder.itemView.setAlpha(0),咱們都知道,在DefaultItemAnimator中,add動畫是一個漸現的過程,這裏先將ItemView的alpha值設置0就容易理解了。

(3). animateMove

  而後,咱們在來看一下animateMove方法的實現:

@Override
    public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
            int toX, int toY) {
        final View view = holder.itemView;
        fromX += (int) holder.itemView.getTranslationX();
        fromY += (int) holder.itemView.getTranslationY();
        resetAnimation(holder);
        int deltaX = toX - fromX;
        int deltaY = toY - fromY;
        if (deltaX == 0 && deltaY == 0) {
            dispatchMoveFinished(holder);
            return false;
        }
        if (deltaX != 0) {
            view.setTranslationX(-deltaX);
        }
        if (deltaY != 0) {
            view.setTranslationY(-deltaY);
        }
        mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
        return true;
    }
複製代碼

  相對來講,animateMove的實現比其餘操做都要複雜一些,可是再怎麼複雜,其實就作了兩件事:

  1. 根據狀況,來設置ViewtranslationX或者translationY
  2. mPendingMoves數組裏面添加一個MoveInfo,用於move動畫使用。

(4). animateChange

  最後,咱們在來看看animateChange方法的實現:

public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
            int fromX, int fromY, int toX, int toY) {
        if (oldHolder == newHolder) {
            // Don't know how to run change animations when the same view holder is re-used. // run a move animation to handle position changes. return animateMove(oldHolder, fromX, fromY, toX, toY); } final float prevTranslationX = oldHolder.itemView.getTranslationX(); final float prevTranslationY = oldHolder.itemView.getTranslationY(); final float prevAlpha = oldHolder.itemView.getAlpha(); resetAnimation(oldHolder); int deltaX = (int) (toX - fromX - prevTranslationX); int deltaY = (int) (toY - fromY - prevTranslationY); // recover prev translation state after ending animation oldHolder.itemView.setTranslationX(prevTranslationX); oldHolder.itemView.setTranslationY(prevTranslationY); oldHolder.itemView.setAlpha(prevAlpha); if (newHolder != null) { // carry over translation values resetAnimation(newHolder); newHolder.itemView.setTranslationX(-deltaX); newHolder.itemView.setTranslationY(-deltaY); newHolder.itemView.setAlpha(0); } mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY)); return true; } 複製代碼

  而change操做比較特殊,哪裏特殊呢?主要有兩點:

  1. 它涉及到從舊狀態變成新狀態,因此須要考慮兩個ItemView的存在。
  2. 在作change操做時,可能會作move操做。因此在這裏還設置了ItemViewtranslation

  這裏只是簡單的介紹了ItemView可能同時進行change操做和move操做,待會在將動畫實現時,咱們來看一下DefaultItemAnimator是怎麼實現兩個動畫同時進行的。

(5). runPendingAnimations

  咱們往數組裏面添加了不少須要作動畫的元素,可是何時開始執行這些動畫呢?其實咱們在RecyclerView 源碼分析(四) - RecyclerView的動畫機制這篇文章裏面已經分析了RecyclerView是怎麼開始動畫的。其實就是回調了ItemAnimator的一個方法--runPendingAnimations方法,以前咱們添加的全部動畫都在方法裏面執行。咱們在看runPendingAnimations方法以前,咱們有幾個問題,咱們帶着問題去看思路會更加的清晰,一共兩個問題:

  1. DefaultItemAnimator是怎麼實現每種操做的動畫呢?
  2. 咱們知道,在RecyclerView中,某些操做的動畫是時序,好比說,必須在remove動畫執行完畢以後,纔會執行move動畫,這個又是怎麼實現呢?

  經過閱讀runPendingAnimations方法的源碼,咱們能夠將它的源碼分爲4個部分,分別以下:

  1. remove動畫的執行
  2. move動畫的執行
  3. change動畫的執行
  4. add動畫的執行

  咱們分別來看看這四部分的代碼。

A. remove動畫的執行

// First, remove stuff
        for (ViewHolder holder : mPendingRemovals) {
            animateRemoveImpl(holder);
        }
        mPendingRemovals.clear();
複製代碼

  經過上面的代碼,咱們知道,remove動畫的實現關鍵在於animateRemoveImpl方法,咱們來看看animateRemoveImpl方法的實現:

private void animateRemoveImpl(final ViewHolder holder) {
        final View view = holder.itemView;
        final ViewPropertyAnimator animation = view.animate();
        mRemoveAnimations.add(holder);
        animation.setDuration(getRemoveDuration()).alpha(0).setListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animator) {
                        dispatchRemoveStarting(holder);
                    }

                    @Override
                    public void onAnimationEnd(Animator animator) {
                        animation.setListener(null);
                        view.setAlpha(1);
                        dispatchRemoveFinished(holder);
                        mRemoveAnimations.remove(holder);
                        dispatchFinishedWhenDone();
                    }
                }).start();
    }
複製代碼

  在這裏,咱們知道,ItemView的remove動畫是經過ViewPropertyAnimator來實現的。在這裏咱們須要注意幾點:

  1. 在動畫開始以前,往mRemoveAnimations數組裏面添加了一個元素,主要是用於結束動畫的操做。當咱們在結束動畫時,發現mRemoveAnimations數組裏面還有元素,表示還有remove動畫沒有執行完畢,因此結束它。
  2. onAnimationEnd方法裏面,咱們分別調用了dispatchRemoveFinished方法和dispatchFinishedWhenDone。這兩步操做是必須的,並且注意他們的時序。

  至於ViewPropertyAnimator實現動畫爲何須要這樣來講,這不是本文的重點,這裏就不介紹了,你們有興趣能夠去簡單學習一下。

B. move動畫

if (movesPending) {
            final ArrayList<MoveInfo> moves = new ArrayList<>();
            moves.addAll(mPendingMoves);
            mMovesList.add(moves);
            mPendingMoves.clear();
            Runnable mover = new Runnable() {
                @Override
                public void run() {
                    for (MoveInfo moveInfo : moves) {
                        animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
                                moveInfo.toX, moveInfo.toY);
                    }
                    moves.clear();
                    mMovesList.remove(moves);
                }
            };
            if (removalsPending) {
                View view = moves.get(0).holder.itemView;
                ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
            } else {
                mover.run();
            }
        }
複製代碼

  這部分的代碼相對來講要複雜一點點,咱們來簡單的分析一下。上面的代碼,咱們須要注意如以下幾點:

  1. 將move動畫相關的動畫元素原封不動的添加到mMovesList數組裏面。這樣作的目的是,由於move動畫的開始有延遲,得等待remove動畫執行完畢以後才執行。因此,存在咱們在結束動畫時,move動畫尚未開始執行的狀況,因此得先添加進去,以便結束move動畫。
  2. 從這裏咱們就能夠知道,DefaultItemAnimator是怎麼解決動畫的時序問題。這裏經過ViewCompatpostOnAnimationDelayed來作一個延遲執行,保證rmove動畫執行完畢才執行move動畫。

  而後,咱們在來看看animateMoveImpl方法是怎麼實現move動畫的:

void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        final View view = holder.itemView;
        final int deltaX = toX - fromX;
        final int deltaY = toY - fromY;
        if (deltaX != 0) {
            view.animate().translationX(0);
        }
        if (deltaY != 0) {
            view.animate().translationY(0);
        }
        // TODO: make EndActions end listeners instead, since end actions aren't called when // vpas are canceled (and can't end them. why?)
        // need listener functionality in VPACompat for this. Ick.
        final ViewPropertyAnimator animation = view.animate();
        mMoveAnimations.add(holder);
        animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animator) {
                dispatchMoveStarting(holder);
            }

            @Override
            public void onAnimationCancel(Animator animator) {
                if (deltaX != 0) {
                    view.setTranslationX(0);
                }
                if (deltaY != 0) {
                    view.setTranslationY(0);
                }
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                animation.setListener(null);
                dispatchMoveFinished(holder);
                mMoveAnimations.remove(holder);
                dispatchFinishedWhenDone();
            }
        }).start();
    }
複製代碼

  animateMoveImpl方法實現move動畫其實跟remove的實現差很少,都是經過ViewPropertyAnimator來實現的,因此這裏就再也不分析了。

C. change動畫

  而後咱們再來看看change動畫的實現:

if (changesPending) {
            final ArrayList<ChangeInfo> changes = new ArrayList<>();
            changes.addAll(mPendingChanges);
            mChangesList.add(changes);
            mPendingChanges.clear();
            Runnable changer = new Runnable() {
                @Override
                public void run() {
                    for (ChangeInfo change : changes) {
                        animateChangeImpl(change);
                    }
                    changes.clear();
                    mChangesList.remove(changes);
                }
            };
            if (removalsPending) {
                ViewHolder holder = changes.get(0).oldHolder;
                ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
            } else {
                changer.run();
            }
        }
複製代碼

  上面的代碼跟move動畫那部分的實現都差很少,這裏就再也不分析了,咱們這裏重點是看animateChangeImpl方法:

void animateChangeImpl(final ChangeInfo changeInfo) {
        final ViewHolder holder = changeInfo.oldHolder;
        final View view = holder == null ? null : holder.itemView;
        final ViewHolder newHolder = changeInfo.newHolder;
        final View newView = newHolder != null ? newHolder.itemView : null;
        if (view != null) {
            final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
                    getChangeDuration());
            mChangeAnimations.add(changeInfo.oldHolder);
            oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
            oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
            oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animator) {
                    dispatchChangeStarting(changeInfo.oldHolder, true);
                }

                @Override
                public void onAnimationEnd(Animator animator) {
                    oldViewAnim.setListener(null);
                    view.setAlpha(1);
                    view.setTranslationX(0);
                    view.setTranslationY(0);
                    dispatchChangeFinished(changeInfo.oldHolder, true);
                    mChangeAnimations.remove(changeInfo.oldHolder);
                    dispatchFinishedWhenDone();
                }
            }).start();
        }
        if (newView != null) {
            final ViewPropertyAnimator newViewAnimation = newView.animate();
            mChangeAnimations.add(changeInfo.newHolder);
            newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
                    .alpha(1).setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationStart(Animator animator) {
                            dispatchChangeStarting(changeInfo.newHolder, false);
                        }
                        @Override
                        public void onAnimationEnd(Animator animator) {
                            newViewAnimation.setListener(null);
                            newView.setAlpha(1);
                            newView.setTranslationX(0);
                            newView.setTranslationY(0);
                            dispatchChangeFinished(changeInfo.newHolder, false);
                            mChangeAnimations.remove(changeInfo.newHolder);
                            dispatchFinishedWhenDone();
                        }
                    }).start();
        }
    }
複製代碼

  change跟其餘比較起來,實現起來就複雜的多。咱們來簡單的分析一下:

  1. animateChangeImpl方法裏面,主要是有兩個動畫在執行,一個是舊的ItemView漸隱動畫,一個是新的ItemView漸現動畫。
  2. 在change動畫裏面,同時包含了兩部分屬性的動畫,一個是位置的漸變,一個是透明度的漸變。從這裏,咱們就能夠看到,一個在作change動畫的ItemView也有可能作move動畫。因此,在runPendingAnimations方法裏面,4個步驟獨立執行,一個ViewHolder不可能同時走了其中兩個或者兩個以上的步驟。

D. add動畫

  這裏add動畫沒有特殊的操做,就是簡單的漸現,咱們就不作多餘的介紹了。

5. 自定義ItemAnimator

  你們想要自定以一個ItemAnimator,第一個想法就是先去網上搜索一下自定以ItemAnimator的基本步驟。我相信你們搜索到的都是一開始就叫咱們實現不少不少的方法,又不知道這些的做用,有些可能還簡單介紹了一下每一個方法的做用,可是根本不知道這個方法的具體實現。爲何須要這麼來實現動畫呢?這些都是網上大多數文章沒有介紹清楚的。

  這裏,我提出一種自定義ItemAnimator的解決方案,有多是目前最簡單的方案。

  一般上面的源碼的學習,咱們瞭解了DefaultItemAnimator的實現原理。因此,咱們自定義一個ItemAnimator,能夠參考DefaultItemAnimator的實現。從而,咱們自定義一個ItemAnimator,須要解決以下幾個問題:

  1. 須要考慮動畫執行的時序。
  2. 須要考慮結束動畫。
  3. 實現每種動畫的實現邏輯。

  從上面的問題中,咱們發現問題1和問題2,DefaultItemAnimator已經幫咱們實現了,咱們只須要解決問題3就好了。因此,個人解決方案就是:**將DefaultItemAnimator的拷貝出來,咱們只須要改寫animateRemoveImplanimateAddImplanimateMoveImplanimateChangeImpl這四個方法就好了。**一般來講,change動畫和move動畫,咱們都不會改寫,因此重點在於add動畫和remove動畫。

  如今,我來給你們看一下我自定義的一個動畫,效果以下:

  關於代碼部分,我幾乎沒有動DefaultItemAnimator的架構,只是改寫了animateRemoveImpl方法和animateAddImpl方法,咱們來看看:

private void animateRemoveImpl(final ViewHolder holder) {
        final View view = holder.itemView;
        final ViewPropertyAnimator animation = view.animate();
        mRemoveAnimations.add(holder);
        animation.setDuration(getRemoveDuration()).translationX(view.getRootView().getWidth()).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animator) {
                dispatchRemoveStarting(holder);
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                animation.setListener(null);
                view.setTranslationX(0);
                dispatchRemoveFinished(holder);
                mRemoveAnimations.remove(holder);
                dispatchFinishedWhenDone();
            }
        }).start();
    }
    void animateAddImpl(final ViewHolder holder) {
        final View view = holder.itemView;
        final ViewPropertyAnimator animation = view.animate();
        mAddAnimations.add(holder);
        animation.translationX(0).setDuration(getAddDuration())
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animator) {
                        dispatchAddStarting(holder);
                    }

                    @Override
                    public void onAnimationCancel(Animator animator) {
                        view.setAlpha(0);
                    }

                    @Override
                    public void onAnimationEnd(Animator animator) {
                        animation.setListener(null);
                        dispatchAddFinished(holder);
                        mAddAnimations.remove(holder);
                        dispatchFinishedWhenDone();
                    }
                }).start();
    }
複製代碼

  實現的邏輯也是很是簡單,就是將DefaultItemAnimator的alpha變化改成了translationX的變化。如上所示,自定義一個ItemAnimator就是這麼簡單!

  若是還有不懂的同窗,能夠去個人github去下載Demo:ItemAnimatorDemo

6. 總結

  關於ItemAnimator的源碼分析到此就已經結束了,我在這裏作一個簡單的總結。

  1. 理解ItemAnimator的原理以前,咱們最好先了解它的三層模型,分別是ItemAnimatorSimpleItemAnimatorDefaultItemAnimator,咱們須要搞清楚每一層到底爲咱們作了哪些事情。
  2. 自定義ItemAnimator咱們只須要拷貝DefaultItemAnimator的代碼,而後根據本身的要求分別改寫animateRemoveImplanimateAddImplanimateMoveImplanimateChangeImpl這四個方法就好了。

  到此爲止,RecyclerView源碼分析系列的文章就結束了。經過這系列的文章中,咱們全方位立體的瞭解了RecyclerView的各個方面,使得咱們對RecyclerView的理解更上一層樓了。固然這個過程當中確定有地方缺乏了,就好比說:ItemTouchHelper或者ItemDecoration,這部分的內容相對來講簡單一點,後續我會推出一個RecyclerView的拓展系列,來補充這部分的內容。

  人非聖賢,孰能無過!各位大佬在閱讀過程當中若是發現錯誤或者有疑惑,均可以提出來,歡迎大佬們斧正!

相關文章
相關標籤/搜索