本文主要讲解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做移动动画。但是这是比较简单的一种场景。