ItemAnimator
类相关方法的分析和总结。
SimpleItemAnimator
相关代码的分析。
DefaultItemAnimator
相关代码的分析。自定义一个
ItemAnimator
。
1. 概述
首先我们来对ItemAnimator
的整个结构做一个简单的概述,方便大家理解。
通常来说,自定义一个ItemAnimator
的过程是:ItemAnimator
->SimpleItemAnimator
-> 自定义ItemAnimator
。包括官方提供的DefaultItemAnimator
也是这样来定义的,那这样来定义有好处的呢?这样定义,结构层次比较清晰,我们自定义ItemAnimator
比较方便,只需要关注动画怎么实现就行。我们来看看这三层分别干了什么:
结构层次 | 作用 |
---|---|
ItemAnimator | 定义ItemAnimator 的模板,自定义这里包括4个抽象方法,也是非常重要的抽象方法,分别是:animateDisappearance 、animateAppearance 、animatePersistence 和animateChange 。 |
SimpleItemAnimator | 实现了四个抽象方法,根据调用4个抽象方法的时机不同,所以会做不同的动画,所以又对外提供了4个抽象方法,分别是:animateRemove 、animateAdd ,animateMove 和animateChange ,分别对应删除、添加、移动和改变的动画。 |
自定义ItemAnimator | 主要实现4种操作的动画,也包括结束动画相关实现 |
而我们在自定义ItemAnimator
时,只需要考虑第三层就OK,上面两层的逻辑谷歌爸爸已经帮我们实现了。自定义过ItemAnimator
的同学应该都知道,尽管只关注第三层,但是实现还是那么麻烦,介于这个问题,我会提出一个非常简单的自定义itemAnimator
的方案。
2. ItemAnimator
我们从上往下,看看每一层都为我们做了哪些事情,首先我们来了解一下ItemAnimator
。ItemAnimator
总的来说比较简单,我们来看看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
应该做什么动画,现在已经一清二楚了,不再像第一层里面那样,一个方法里面可能涉及到多种情况,每种情况可能执行不同的动画。
现在我们分别来看看SimpleItemAnimator
对ItemAnimator
的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动画就需要移动的起始位置和终点位置,这部分的数组才是做动画的真正做动画需要的;第三部分和第四部分是mXXXList
和mXXXAnimations
,通常来说,都是用于结束动画的,本文后面会简单的分析他们。
简单的了解这四部分的成员变量之后,现在我们重点看一下四个抽象方法的实现。
(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
的实现比其他操作都要复杂一些,但是再怎么复杂,其实就做了两件事:
根据情况,来设置
View
的translationX
或者translationY
。向
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操作比较特殊,哪里特殊呢?主要有两点:
它涉及到从旧状态变成新状态,所以需要考虑两个
ItemView
的存在。在做change操作时,可能会做move操作。所以在这里还设置了
ItemView
的translation
。
这里只是简单的介绍了ItemView
可能同时进行change操作和move操作,待会在将动画实现时,我们来看一下DefaultItemAnimator
是怎么实现两个动画同时进行的。
(5). runPendingAnimations
我们往数组里面添加了很多需要做动画的元素,但是什么时候开始执行这些动画呢?其实我们在RecyclerView 源码分析(四) - RecyclerView的动画机制这篇文章里面已经分析了RecyclerView
是怎么开始动画的。其实就是回调了ItemAnimator
的一个方法--runPendingAnimations
方法,之前我们添加的所有动画都在方法里面执行。我们在看runPendingAnimations
方法之前,我们有几个问题,我们带着问题去看思路会更加的清晰,一共两个问题:
DefaultItemAnimator
是怎么实现每种操作的动画呢?我们知道,在
RecyclerView
中,某些操作的动画是时序,比如说,必须在remove动画执行完毕之后,才会执行move动画,这个又是怎么实现呢?
通过阅读runPendingAnimations
方法的源码,我们可以将它的源码分为4个部分,分别如下:
remove动画的执行
move动画的执行
change动画的执行
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
来实现的。在这里我们需要注意几点:
在动画开始之前,往
mRemoveAnimations
数组里面添加了一个元素,主要是用于结束动画的操作。当我们在结束动画时,发现mRemoveAnimations
数组里面还有元素,表示还有remove动画没有执行完毕,所以结束它。在
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(); } }
这部分的代码相对来说要复杂一点点,我们来简单的分析一下。上面的代码,我们需要注意如如下几点:
将move动画相关的动画元素原封不动的添加到
mMovesList
数组里面。这样做的目的是,因为move动画的开始有延迟,得等待remove
动画执行完毕之后才执行。所以,存在我们在结束动画时,move动画还没有开始执行的情况,所以得先添加进去,以便结束move动画。从这里我们就可以知道,
DefaultItemAnimator
是怎么解决动画的时序问题。这里通过ViewCompat
的postOnAnimationDelayed
来做一个延迟执行,保证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跟其他比较起来,实现起来就复杂的多。我们来简单的分析一下:
在
animateChangeImpl
方法里面,主要是有两个动画在执行,一个是旧的ItemView
渐隐动画,一个是新的ItemView
渐现动画。在change动画里面,同时包含了两部分属性的动画,一个是位置的渐变,一个是透明度的渐变。从这里,我们就可以看到,一个在做change动画的
ItemView
也有可能做move
动画。所以,在runPendingAnimations
方法里面,4个步骤独立执行,一个ViewHolder
不可能同时走了其中两个或者两个以上的步骤。
D. add动画
这里add动画没有特殊的操作,就是简单的渐现,我们就不做多余的介绍了。
5. 自定义ItemAnimator
大家想要自定以一个ItemAnimator
,第一个想法就是先去网上搜索一下自定以ItemAnimator
的基本步骤。我相信大家搜索到的都是一开始就叫我们实现很多很多的方法,又不知道这些的作用,有些可能还简单介绍了一下每个方法的作用,但是根本不知道这个方法的具体实现。为什么需要这么来实现动画呢?这些都是网上大多数文章没有介绍清楚的。
这里,我提出一种自定义ItemAnimator
的解决方案,有可能是目前最简单的方案。
通常上面的源码的学习,我们了解了DefaultItemAnimator
的实现原理。所以,我们自定义一个ItemAnimator
,可以参考DefaultItemAnimator
的实现。从而,我们自定义一个ItemAnimator
,需要解决如下几个问题:
需要考虑动画执行的时序。
需要考虑结束动画。
实现每种动画的实现逻辑。
从上面的问题中,我们发现问题1和问题2,DefaultItemAnimator
已经帮我们实现了,我们只需要解决问题3就行了。所以,我的解决方案就是:将DefaultItemAnimator
的拷贝出来,我们只需要改写animateRemoveImpl
、animateAddImpl
、animateMoveImpl
和animateChangeImpl
这四个方法就行了。通常来说,change动画和move动画,我们都不会改写,所以重点在于add动画和remove动画。
现在,我来给大家看一下我自定义的一个动画,效果如下:
作者:琼珶和予
链接:https://www.jianshu.com/p/3d52c5ede093