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

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

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


场景回顾



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


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


image.png

在Item1下面增加两个Item场景,各个layout阶段的布局情况


image.png


我们定义四中布局状态

  • 初始状态
  • 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文件中


image.png

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

image.png

哇呀,原来处理动画的方法只有四个,感觉so easy!


具体实现如下


image.png

讲解下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


image.png

原来Appeard动画此时一分为二


  • 如果preLayoutInfo不为null,执行animateMove动画
  • 反之执行animateAdd动画


8. DefaultItemAnimator.animateMove

image.png


注意 此步骤并没有真正执行动画,而是将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


image.png


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


  1. 首先执行Remove动画
  2. 然后同时执行Move和Change动画
  3. 最后执行Add动画


所以RV执行动画的总时长为removeDuration + Math.max(moveDuration, changeDuration) + addDuration。


至此,RecyclerView的动画原理已经讲解完毕,动画的执行原理,就是根据preLayout和postLayout,ViewHolder的位置来做动画的。但是我还是不明白,哪些ViewHolder执行哪种类型的动画。问题问到点子上了,既然这样,我们通过delte场景来讲解Item具体执行什么动画。


结合场景讲解动画类型



image.png该场景中一共有Item1Item8 8个Item,最终显示给用户看的有Item3Item8 6个Item,那么他们具体都执行了何种类型的动画呢?这里涉及到dispatchLayout和ViewInfoStore两个知识点


image.png

image.png


image.png


image.png

//该案例一定是在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做移动动画。但是这是比较简单的一种场景。

相关文章
|
2月前
|
前端开发 JavaScript API
如何实现两栏布局?这篇文章告诉你所有的细节!
欢迎来到前端入门之旅!这个专栏是为那些对Web开发感兴趣、刚刚开始学习前端的读者们打造的。无论你是初学者还是有一些基础的开发者,我们都会在这里为你提供一个系统而又亲切的学习平台。我们以问答形式更新,为大家呈现精选的前端知识点和最佳实践。通过深入浅出的解释概念,并提供实际案例和练习,让你逐步建立起一个扎实的基础。无论是HTML、CSS、JavaScript还是最新的前端框架和工具,我们都将为你提供丰富的内容和实用技巧,帮助你更好地理解并运用前端开发中的各种技术。
|
4月前
|
XML Java Android开发
Android App开发中集合动画和属性动画的讲解及实战演示(附源码 简单易懂 可直接使用)
Android App开发中集合动画和属性动画的讲解及实战演示(附源码 简单易懂 可直接使用)
27 0
Android App开发中集合动画和属性动画的讲解及实战演示(附源码 简单易懂 可直接使用)
|
6月前
|
Dart 前端开发 容器
带你读《深入浅出Dart》二十五、Widget和布局(1)
带你读《深入浅出Dart》二十五、Widget和布局(1)
|
6月前
|
Dart
带你读《深入浅出Dart》二十五、Widget和布局(2)
带你读《深入浅出Dart》二十五、Widget和布局(2)
|
11月前
|
消息中间件 存储 缓存
RecyclerView 的滚动是怎么实现的?(一)| 解锁阅读源码新姿势
RecyclerView 的滚动是怎么实现的?(一)| 解锁阅读源码新姿势
95 0
Cocos2dx Touch事件原理剖析
Cocos2dx Touch事件原理剖析
139 0
|
XML 前端开发 Android开发
自定义View,有这一篇就够了
自定义View,有这一篇就够了
自定义View,有这一篇就够了
|
API Android开发
Android动画基础详析 | 属性动画基础及ValueAnimator
Android动画基础详析 | 属性动画基础及ValueAnimator
老大爷都能看懂的RecyclerView动画原理
老大爷都能看懂的RecyclerView动画原理
老大爷都能看懂的RecyclerView动画原理
|
缓存 Android开发
RecyclerView高级进阶之优雅地解决瀑布流的两个神坑
RecyclerView高级进阶之优雅地解决瀑布流的两个神坑
RecyclerView高级进阶之优雅地解决瀑布流的两个神坑