RecyclerView动画执行有两篇文章
本文主要讲解RecyclerView 是如何执行动画的。首先贴出删除和增加场景下,他们在dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3各个阶段下的布局情况如下。布局过程可参考RecyclerView dispatchLayout布局原理一文。
场景回顾
RecyclerView dispatchLayout布局原理一文,我讲解了删除Item和增加Item两种情况。现在将它们各种Layout阶段的情况汇总
- 删除Item1、Item2场景,各个layout阶段的布局情况
- 在Item1下面增加两个Item场景,各个layout阶段的布局情况
我们定义四中布局状态
- 初始状态
- LayoutStep1
- LayoutStep2
- LayoutStep3
源码讲解
1. RecyclerView的dispatchLayout
通过源码我们了解到,在dispatchLayoutStep3中RecyclerView会执行动画,代码如下:
//From RecyclerView.java
private void dispatchLayoutStep3() {
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}
2. ViewInfoStore process方法
跟进 process代码。源码位于ViewInfoStore.java文件中
3. ViewInfoStore$InfoRecord Flag
Flags定义在ViewInfoStore$InfoRecord类中
static class InfoRecord {
// disappearing list
static final int FLAG_DISAPPEARED = 1;
// appear in pre layout list
static final int FLAG_APPEAR = 1 << 1;
// pre layout, this is necessary to distinguish null item info
static final int FLAG_PRE = 1 << 2;
// post layout, this is necessary to distinguish null item info
static final int FLAG_POST = 1 << 3;
static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;
}
- FLAG_DISAPPEARED:表示ViewHolder需要做消失动画
- FLAG_DISAPPEARED:表示ViewHolder需要做出现动画
- FLAG_PRE:表示该ViewHolder在初始状态显示在RV上
- FLAG_POST:表示该ViewHolder在LayoutStep3状态显示在RV上
这四种基本FLAG会衍生出以下几种:
- FLAG_APPEAR_AND_DISAPPEAR:表示先做Appear动画然后做DISAPPEAR动画,从源码的注释来看,这种动画毫无意义,忽略掉
- FLAG_PRE_AND_POST:表示ViewHolder在初始状态和LayoutStep3状态一直存在于RV上
- FLAG_APPEAR_PRE_AND_POST:源码注释为Appeared in the layout but not in the adapter (e.g. entered the viewport)。我没太理解,也没有模拟出场景。有大神知道,请评论区告知。
4. 讲解process方法的功能
-
如果Flag含有FLAG_APPEAR_AND_DISAPPEAR,调用callback.unused(viewHolder)
-
步骤1不成立,如果Flag为FLAG_DISAPPEARED,这里有两种情况
2.1. 如果record.preInfo == null,初始状态时该ViewHolder不在RV上,消失动画无意义,调用callback.unused(viewHolder)
2.2. 根据preInfo和postInfo执行消失动画
-
上述不成立,如果Flag含有FLAG_APPEAR_PRE_AND_POST,调用callback.processAppeared(viewHolder, record.preInfo, record.postInfo)
-
上述不成立,如果Flag含有FLAG_PRE_AND_POST,表示一直都在RV上,调用callback.processPersistent(viewHolder, record.preInfo, record.postInfo)
-
上述不成立,如果Flag含有FLAG_PRE,表示初始有,step3没有,显然执行消失动画,调用callback.processDisappeared(viewHolder, record.preInfo, null)
-
上述不成立,如果Flag含有FLAG_POST,表示初始没有,step3有,显然是新增加进来的,调用callback.processAppeared(viewHolder, record.preInfo, record.postInfo)
5.ViewInfoStore$ProccessCallback
哇呀,原来处理动画的方法只有四个,感觉so easy!
具体实现如下
讲解下processAppeard和unused两个方法
- unused不需要做任何动画,直接移除并且放入回收池(此处引出了RecyclerView的回收策略,以后有空再写吧)
- processAppeard方法相对processDisappeard方法复杂一些
6. processAppeard方法
void animateAppearance(ViewHolder itemHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo) {
itemHolder.setIsRecyclable(false);
if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
此时兵分两路,ItemAnimator.animateAppearance和postAnimationRunner
7. DefaultItemAnimator.animateAppearance
原来Appeard动画此时一分为二
- 如果preLayoutInfo不为null,执行animateMove动画
- 反之执行animateAdd动画
8. DefaultItemAnimator.animateMove
注意 此步骤并没有真正执行动画,而是将MoveInfo保存到mPendingMoves,我们前面说到过兵分两路,mPendingMoves保存的动画数据,会在第二路,真正去执行
9. DefaultItemAnimator.animateAdd
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
holder.itemView.setAlpha(0);
mPendingAdditions.add(holder);
return true;
}
啊呀,原来当调用notifyItemInsert做的是一个淡入的动画
注意 animateXXX返回boolean类型,如果返回false,动画将不会执行
10. postAnimationRunner
void postAnimationRunner() {
if (!mPostedAnimatorRunner && mIsAttached) {
ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
mPostedAnimatorRunner = true;
}
}
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
if (mItemAnimator != null) {
mItemAnimator.runPendingAnimations();
}
mPostedAnimatorRunner = false;
}
};
最终调用到ItemAnimator.runPendingAnimations
11. DefaultItemAnimator.runPendingAnimations
代码有点长,逻辑很清晰简单,按照顺序执行动画
- 首先执行Remove动画
- 然后同时执行Move和Change动画
- 最后执行Add动画
所以RV执行动画的总时长为removeDuration + Math.max(moveDuration, changeDuration) + addDuration。
至此,RecyclerView的动画原理已经讲解完毕,动画的执行原理,就是根据preLayout和postLayout,ViewHolder的位置来做动画的。但是我还是不明白,哪些ViewHolder执行哪种类型的动画。问题问到点子上了,既然这样,我们通过delte场景来讲解Item具体执行什么动画。
结合场景讲解动画类型
该场景中一共有Item1Item8 8个Item,最终显示给用户看的有Item3Item8 6个Item,那么他们具体都执行了何种类型的动画呢?这里涉及到dispatchLayout和ViewInfoStore两个知识点
//该案例一定是在remove的场景下,从attachedScrap中拿ViewHolder去执行消失动画
private void addAnimatingView(ViewHolder viewHolder) {
final View view = viewHolder.itemView;
final boolean alreadyParented = view.getParent() == this;
mRecycler.unscrapView(getChildViewHolder(view));
if (viewHolder.isTmpDetached()) {
// re-attach
mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
} else if (!alreadyParented) {
mChildHelper.addView(view, true);
} else {
mChildHelper.hide(view);
}
}
结论很简单 Item1-Item2做消失动画、Item3-Item8做移动动画。但是这是比较简单的一种场景。
最后感谢美团盛书强同学提供的代码生成图片工具,让本文可以更顺利的写成。
如果大佬想鼓励我一下的话,分享,点赞,在看都随你了~