美文网首页Android技术知识Android知识
RecyclerView 源码分析(八) - ItemAnima

RecyclerView 源码分析(八) - ItemAnima

作者: 琼珶和予 | 来源:发表于2019-02-10 21:58 被阅读26次

      ItemAnimator作为RecyclerView的主要组成部分之一,其重要性自然不可言喻。因为ItemAnimator的存在,所以出现了很多精彩纷呈的动画,这使得RecyclerView更加惹人喜爱。因此,学习ItemAnimator的源码是势在必行的,因为我们了解原理之后,就可以自定义动画了,不再受人束缚。
      本文参考资料:

    1. recyclerview-animators
    2. RecyclerView 源码分析(四) - RecyclerView的动画机制

      本文打算才如下几个部分来分析ItemAnimator的源码:

    1. ItemAnimator类相关方法的分析和总结。
    2. SimpleItemAnimator相关代码的分析。
    3. DefaultItemAnimator相关代码的分析。
    4. 自定义一个ItemAnimator

    1. 概述

      首先我们来对ItemAnimator的整个结构做一个简单的概述,方便大家理解。
      通常来说,自定义一个ItemAnimator的过程是:ItemAnimator ->SimpleItemAnimator -> 自定义ItemAnimator。包括官方提供的DefaultItemAnimator也是这样来定义的,那这样来定义有好处的呢?这样定义,结构层次比较清晰,我们自定义ItemAnimator比较方便,只需要关注动画怎么实现就行。我们来看看这三层分别干了什么:

    结构层次 作用
    ItemAnimator 定义ItemAnimator的模板,自定义这里包括4个抽象方法,也是非常重要的抽象方法,分别是:animateDisappearanceanimateAppearanceanimatePersistenceanimateChange
    SimpleItemAnimator 实现了四个抽象方法,根据调用4个抽象方法的时机不同,所以会做不同的动画,所以又对外提供了4个抽象方法,分别是:animateRemoveanimateAddanimateMoveanimateChange,分别对应删除、添加、移动和改变的动画。
    自定义ItemAnimator 主要实现4种操作的动画,也包括结束动画相关实现

      而我们在自定义ItemAnimator时,只需要考虑第三层就OK,上面两层的逻辑谷歌爸爸已经帮我们实现了。自定义过ItemAnimator的同学应该都知道,尽管只关注第三层,但是实现还是那么麻烦,介于这个问题,我会提出一个非常简单的自定义itemAnimator的方案。

    2. ItemAnimator

      我们从上往下,看看每一层都为我们做了哪些事情,首先我们来了解一下ItemAnimatorItemAnimator总的来说比较简单,我们来看看ItemAnimator几个方法:

    方法名 作用
    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的过程。
      在正式看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应该做什么动画,现在已经一清二楚了,不再像第一层里面那样,一个方法里面可能涉及到多种情况,每种情况可能执行不同的动画。
    &esmp; 现在我们分别来看看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的拓展系列,来补充这部分的内容。
      人非圣贤,孰能无过!各位大佬在阅读过程中如果发现错误或者有疑惑,都可以提出来,欢迎大佬们斧正!

    相关文章

      网友评论

        本文标题:RecyclerView 源码分析(八) - ItemAnima

        本文链接:https://www.haomeiwen.com/subject/nghasqtx.html