老大爷都能看懂的RecyclerView动画原理之二

1,674 阅读4分钟

RecyclerView动画执行有两篇文章

  1. 老大爷都能看懂的RecyclerView动画原理之一
  2. 老大爷都能看懂的RecyclerView动画原理之二

本文主要讲解RecyclerView 是如何执行动画的。首先贴出删除和增加场景下,他们在dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3各个阶段下的布局情况如下。布局过程可参考RecyclerView dispatchLayout布局原理一文。

场景回顾

RecyclerView dispatchLayout布局原理一文,我讲解了删除Item和增加Item两种情况。现在将它们各种Layout阶段的情况汇总

  1. 删除Item1、Item2场景,各个layout阶段的布局情况

删除场景

  1. 在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方法的功能

  1. 如果Flag含有FLAG_APPEAR_AND_DISAPPEAR,调用callback.unused(viewHolder)

  2. 步骤1不成立,如果Flag为FLAG_DISAPPEARED,这里有两种情况

    2.1. 如果record.preInfo == null,初始状态时该ViewHolder不在RV上,消失动画无意义,调用callback.unused(viewHolder)

    2.2. 根据preInfo和postInfo执行消失动画

  3. 上述不成立,如果Flag含有FLAG_APPEAR_PRE_AND_POST,调用callback.processAppeared(viewHolder, record.preInfo, record.postInfo)

  4. 上述不成立,如果Flag含有FLAG_PRE_AND_POST,表示一直都在RV上,调用callback.processPersistent(viewHolder, record.preInfo, record.postInfo)

  5. 上述不成立,如果Flag含有FLAG_PRE,表示初始有,step3没有,显然执行消失动画,调用callback.processDisappeared(viewHolder, record.preInfo, null)

  6. 上述不成立,如果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

代码有点长,逻辑很清晰简单,按照顺序执行动画

  1. 首先执行Remove动画
  2. 然后同时执行Move和Change动画
  3. 最后执行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做移动动画。但是这是比较简单的一种场景。

最后感谢美团盛书强同学提供的代码生成图片工具,让本文可以更顺利的写成。

如果大佬想鼓励我一下的话,分享,点赞,在看都随你了~