Item Animation的基本原理
我们都知道,给RecyclerView
添加Item Animation的方法是setItemAnimator(ItemAnimator animator)
。因此我们可以以抽象类ItemAnimator
为切入点,看看Recyclerview
的Item Animation是怎样实现的。
首先列举一下ItemAnimator
中几个比较重要的方法签名:
-
public ItemHolderInfo recordPreLayoutInformation(State state,ViewHolder viewHolder,int changeFlags,List<Object> payloads)
public ItemHolderInfo recordPostLayoutInformation(State state, ViewHolder viewHolder)
这两个方法是成对出现的,分别用于记录同一个ViewHolder在layout执行前后view位置等信息,分别对应了动画开始前的view状态和动画结束后的view状态。
-
public abstract boolean animateDisappearance(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)
public abstract boolean animateAppearance(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)
public abstract boolean animatePersistence(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)
public abstract boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)
这四个方法都是由
RecyclerView
调用,调用时机分别为在执行layout过程之后viewHolder
从RecyclerView
中移除(从有到无)、出现(从无到有)、不变(同一个viewHolder
但是尺寸、位置可能发生变化)、改变(从oldHolder
变为newHolder
,viewHolder
发生了变化)。
这四个方法中都包含preLayoutInfo
和postLayoutInfo
,这两个参数就是由前面recordPreLayoutInformation
和recordPostLayoutInformation
方法计算出来的viewHolder
信息。我们就可以根据viewHolder
的初态(preLayoutInfo
)和终态(postLayoutInfo
)实现自定义的动画。但是动画并不是在这个方法中启动,在这四个方法中我们仅仅是根据preLayoutInfo
和postLayoutInfo
判断是否需要动画和需要什么动画。如果不需要执行动画,就必须在这四个方法中调用dispatchAnimationFinished(ViewHolder)
方法并且最后的return值必须为false
;反之如果需要执行动画return值就必须为true
,之后系统就会调用runPendingAnimations()
方法,并在runPendingAnimations()
中启动各种动画,在动画执行完毕后也必须要调用dispatchAnimationFinished(ViewHolder)
方法。
abstract public void runPendingAnimations()
如果上面四个方法中有一个的返回值是true
,在下一帧的时候就会执行此方法,所有的动画都可以在此方法中启动。
abstract public void endAnimation(ViewHolder item)
-
abstract public void endAnimations()
结束动画,并确保各个viewHolder都处于最终态。
到此为止,我们可以缕一下整个过程。
首先RecyclerView
在执行layout前后会调用recordPreLayoutInformation
和recordPostLayoutInformation
方法,并保存layout前后viewHodler
的位置等信息;然后根据layout的情况执行animateXXX
方法,我们在animateXXX
方法中决定是否执行动画和执行何种动画;如果需要执行动画,则可以在runPendingAnimations
方法中启动动画;最后在endAnimation
和endAnimations
方法中确保viewHolder
处于最终态。
快速实现自定义Item Animation
问当今如何code最快,那必然是ctrl-C加上ctrl-V。(开个玩笑~~~)
不过现在讲的是如何快速的自定义Item Animation,基于学习的目的,那当然还是需要copy一些代码的嘛。至于以后项目中需要或者兴趣使然要深入自定义Item Animation就需要自己参照原有实现进行封装了。
废话不多说,下面进入正题!!!
android support v7包里已经包含了一个Item Animation的默认实现:android.support.v7.widget.DefaultItemAnimator
,DefaultItemAnimator
继承了SimpleItemAnimator
,而SimpleItemAnimator又继承了RecyclerView.ItemAnimator
。DefaultItemAnimator
实现了在添加、移除item时View的透明度发生变化,在移动item时view的位置发生变化。我们要做的仅仅是对DefaultItemAnimator
进行局部的修改~~~
recordPreLayoutInformation和recordPostLayoutInformation
在ItemAnimator
中已经实现,animateDisappearance
、animateAppearance
、animatePersistence
和animateChange
在SimpleItemAnimator
中也已实现,runPendingAnimations
在DefaultItemAnimator
中已经实现,我们用这些默认实现就可以了。以添加Item动画为例,我们只要修改如下3个地方就可以实现Item旋转进入动画(仅列出部分关键代码,注释为原代码,////中间为修改后的代码):
-
设置动画的初始值,将view设置为旋转180度
@Override public boolean animateAdd(final ViewHolder holder) { resetAnimation(holder); /**ViewCompat.setAlpha(holder.itemView, 0);**/ // ViewCompat.setRotation(holder.itemView, 180); // mPendingAdditions.add(holder); return true; }
2. 启动动画,让view再转180度。并在动画取消时将view设置为终态,也就是旋转0度
```
private void animateAddImpl(final RecyclerView.ViewHolder holder) {
...
/**animation.alpha(1)**/
//
animation.rotationBy(180)
//
.setDuration(getAddDuration()).
setListener(new VpaListenerAdapter() {
...
@Override
public void onAnimationCancel(View view) {
/**ViewCompat.setAlpha(view, 1);**/
//
ViewCompat.setRotation(view, 0);
//
}
...
}).start();
}
-
结束动画,确保view在动画结束或者取消后能回到终态,也就是旋转0度。(这里省略了endAnimations()方法的修改)
@Override public void endAnimation(RecyclerView.ViewHolder item) { ... if (mPendingAdditions.remove(item)) { /**ViewCompat.setAlpha(view, 1);**/ // ViewCompat.setRotation(view, 0); // dispatchAddFinished(item); } ... for (int i = mAdditionsList.size() - 1; i >= 0; i--) { ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i); if (additions.remove(item)) { /**ViewCompat.setAlpha(view, 1);**/ // ViewCompat.setRotation(view, 0); // dispatchAddFinished(item); if (additions.isEmpty()) { mAdditionsList.remove(i); } } } ... }
通过以上3步,我们就是实现了Item旋转进入的动画,以下是演示效果。
![Demo 演示效果](https://img.haomeiwen.com/i289461/7b20ae37379c1199.gif?imageMogr2/auto-orient/strip)
>[Demo地址](https://github.com/lileibuaa/CustomItemAnimator)
**再次强调,以上步骤仅适用于学习。在项目中使用还需自己参照实现自己想要的效果。**
PS:顺带说下如何解决调用`notifyDataSetChanged`后,没有触发Item Animation的问题。。
要想在调用`notifyDataSetChanged`之后触发Item Animation,必须要在自定义adapter中调用方法`setHasStableIds(true)`;并实现`public long getItemId(int position)`方法返回各Item的id。。至于为啥,留待以后再探究吧。。。请原谅我这个拖延症重度患者。
网友评论