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的拓展系列,来补充这部分的内容。

  人非圣贤,孰能无过!各位大佬在阅读过程当中若是发现错误或者有疑惑,均可以提出来,欢迎大佬们斧正!

相关文章
相关标签/搜索