软件编程
位置:首页>> 软件编程>> Android编程>> Android 源码浅析RecyclerView ItemAnimator

Android 源码浅析RecyclerView ItemAnimator

作者:孙先森Blog  发布时间:2022-04-20 19:47:41 

标签:Android,RecyclerView,ItemAnimator

前言

在这个系列博客的第二篇的最后部分提到了预布局,在预布局阶段,计算剩余空间时会把将要移除的 ViewHolder 忽略,从而计算出递补的 ViewHolder,在 ViewHolder 移除、新增、更新时都可以触发默认动画(也可以自定义动画),那么动画部分到底是怎么实现的呢?本篇博客将针对 ItemAnimator 的运作流程部分源码进行分析。

源码分析

前置基础

一般我们给 RecyclerView 设置动画会调用 setItemAnimator 方法,直接看一下源码:

RecyclerView.java

// 默认动画 DefaultItemAnimator
ItemAnimator mItemAnimator = new DefaultItemAnimator();
public void setItemAnimator(@Nullable ItemAnimator animator) {
   if (mItemAnimator != null) { // 先结束动画,取消监听
       mItemAnimator.endAnimations();
       mItemAnimator.setListener(null);
   }
   mItemAnimator = animator; // 赋值
   if (mItemAnimator != null) { // 重新设置监听
       mItemAnimator.setListener(mItemAnimatorListener);
   }
}

可以看出 RecyclerView 默认提供了 DefaultItemAnimator,先不着急分析它,首先我们要分析出动画的执行流程以及动画的信息是怎么处理的。先了解以下这么几个类作为基础。

ItemHolderInfo

RecylerView.java

public static class ItemHolderInfo {
   public int left;
   public int top;
   public int right;
   public int bottom;
   @AdapterChanges
   public int changeFlags;
   public ItemHolderInfo() {
   }
   public ItemHolderInfo setFrom(@NonNull RecyclerView.ViewHolder holder) {
       return setFrom(holder, 0);
   }
   @NonNull
   public ItemHolderInfo setFrom(@NonNull RecyclerView.ViewHolder holder,
           @AdapterChanges int flags) {
       final View view = holder.itemView;
       this.left = view.getLeft();
       this.top = view.getTop();
       this.right = view.getRight();
       this.bottom = view.getBottom();
       return this;
   }
}

ItemHolderInfo 作为 RecyclerView 的内部类,代码非常简单,向外暴露 setFrom 方法,用于存储 ViewHolder 的位置信息;

InfoRecord

InfoRecord 是 ViewInfoStore 的内部类(下一小节分析),代码也非常简单:

ViewInfoStore.java

static class InfoRecord {
   // 一些 Flag 定义
   static final int FLAG_DISAPPEARED = 1; // 消失
   static final int FLAG_APPEAR = 1 << 1; // 出现
   static final int FLAG_PRE = 1 << 2; // 预布局
   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;
   // 这个 flags 要记住!!
   // 后面多次对其进行赋值,且执行动画时也根据 flags 来判断动画类型;
   int flags;
   // ViewHolder 坐标信息
   RecyclerView.ItemAnimator.ItemHolderInfo preInfo; // 预布局阶段的
   RecyclerView.ItemAnimator.ItemHolderInfo postInfo; // 真正布局阶段的
   // 池化 提高效率
   static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
   // ...
   // 其内部的一些方法都是复用池相关 特别简单 就不贴了
}

不难看出,InfoRecord 功能和他的名字一样信息记录,主要记录了预布局、真正布局两个阶段的 ViewHodler 的位置信息(ItemHolderInfo)。

ViewInfoStore

class ViewInfoStore {
   // 将 ViewHodler 和 InfoRecord 以键值对形式存储
   final SimpleArrayMap<RecyclerView.ViewHolder, InfoRecord> mLayoutHolderMap =
           new SimpleArrayMap<>();
   // 根据坐标存储 ViewHodler 看名字也看得出是 旧的,旧是指:
   // 1.viewHolder 被隐藏 但 未移除
   // 2.隐藏item被更改
   // 3.预布局跳过的 item
   final LongSparseArray<RecyclerView.ViewHolder> mOldChangedHolders = new LongSparseArray<>();
   // mLayoutHolderMap 中添加一项 如果有就改变 InfoRecord 的值
   // 下面很多方法都是类似功能 下面的就不贴 if 里面那段了
   void addToPreLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
       InfoRecord record = mLayoutHolderMap.get(holder);
       if (record == null) { // 没有就构建一个 加入 map
           record = InfoRecord.obtain();
           mLayoutHolderMap.put(holder, record);
       }
       record.preInfo = info;
       record.flags |= FLAG_PRE; // 跟方法名对应的 flag
   }
   // 调用 popFromLayoutStep 传递 FLAG_PRE
   RecyclerView.ItemAnimator.ItemHolderInfo popFromPreLayout(RecyclerView.ViewHolder vh) {
       return popFromLayoutStep(vh, FLAG_PRE);
   }
   // 调用 popFromLayoutStep 传递 FLAG_POST
   RecyclerView.ItemAnimator.ItemHolderInfo popFromPostLayout(RecyclerView.ViewHolder vh) {
       return popFromLayoutStep(vh, FLAG_POST);
   }
   // 上面两个方法都调用的这里 flag 传递不同
   private RecyclerView.ItemAnimator.ItemHolderInfo popFromLayoutStep(RecyclerView.ViewHolder vh, int flag) {
       int index = mLayoutHolderMap.indexOfKey(vh);
       if (index < 0) {
           return null;
       }
       // 从map中获取
       final InfoRecord record = mLayoutHolderMap.valueAt(index);
       if (record != null && (record.flags & flag) != 0) {
           record.flags &= ~flag;
           final RecyclerView.ItemAnimator.ItemHolderInfo info;
           if (flag == FLAG_PRE) { // 根据 flag 获取对应的 ItemHolderInfo
               info = record.preInfo;
           } else if (flag == FLAG_POST) {
               info = record.postInfo;
           } else {
               throw new IllegalArgumentException("Must provide flag PRE or POST");
           }
           // 如果没有包含两个阶段的flag 直接回收
           if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) {
               mLayoutHolderMap.removeAt(index);
               InfoRecord.recycle(record);
           }
           return info;
       }
       return null;
   }
   // 向 mOldChangedHolders 添加一个 holder
   void addToOldChangeHolders(long key, RecyclerView.ViewHolder holder) {
       mOldChangedHolders.put(key, holder);
   }
   // 和 addToPreLayout 方法类似 flags 不同
   void addToAppearedInPreLayoutHolders(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
       InfoRecord record = mLayoutHolderMap.get(holder);
       // ...
       record.flags |= FLAG_APPEAR;
       record.preInfo = info;
   }
   // 和 addToPreLayout 方法类似 flags 不
   // 注意这里的方法名 是添加的 post-layout 真正布局阶段的信息
   void addToPostLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
       InfoRecord record = mLayoutHolderMap.get(holder);
       // ...
       record.postInfo = info; // 这里赋值的是 postInfo
       record.flags |= FLAG_POST;
   }
   // 这里直接拿到 InfoRecord 修改了 flag
   void addToDisappearedInLayout(RecyclerView.ViewHolder holder) {
       InfoRecord record = mLayoutHolderMap.get(holder);
       // ...
       record.flags |= FLAG_DISAPPEARED;
   }
   // 这里直接拿到 InfoRecord 修改了 flag
   void removeFromDisappearedInLayout(RecyclerView.ViewHolder holder) {
       InfoRecord record = mLayoutHolderMap.get(holder);
       // ...
       record.flags &= ~FLAG_DISAPPEARED;
   }
   // 移除 两个容器都移除
   void removeViewHolder(RecyclerView.ViewHolder holder) {
       //...
       mOldChangedHolders.removeAt(i);
       //...
       final InfoRecord info = mLayoutHolderMap.remove(holder);
   }
   // 这里其实是 动画开始的入口
   void process(ProcessCallback callback) {
       // 倒着遍历 mLayoutHolderMap
       for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
           final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
           final InfoRecord record = mLayoutHolderMap.removeAt(index);
           // 取出 InfoRecord 根据 flag 和 两个阶段位置信息 进行判断 触发对应的 callback 回调方法
           if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
               callback.unused(viewHolder);
           } // 一大堆判断就先省略了,后面会提到 ...
           // ...
           // 最后回收
           InfoRecord.recycle(record);
       }
   }
   // ...
}

ViewInfoStore,字面翻译为 View信息商店,类名就体现出了他的功能,主要提供了对 ViewHolder 的 InfoRecord 存储以及修改,并且提供了动画触发的入口。

ProcessCallback

还有最后一个类需要了解,也就是上面 ViewInfoStore 最后一个方法 process 中用到的 callback,直接看源码:

ViewInfoStore.java

//前三个需要做动画的方法传入了 viewHolder 以及其预布局、真正布局两个阶段的位置信息
interface ProcessCallback {
   // 进行消失
   void processDisappeared(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo postInfo);
   // 进行出现
   void processAppeared(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo postInfo);
   // 持续 也就是 不变 或者 数据相同大小改变的情况
   void processPersistent(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo postInfo);
   // 未使用
   void unused(RecyclerView.ViewHolder holder);
}

ProcessCallback 在 RecyclerView 有默认实现,这个待会再详细分析,看 callback 的方法名也能略知一二,分别对应 ViewHolder 做动画的几种情况;

那么从前三个方法的参数中也能推断出,ViewHolder 做动画时,动画的数据也是从 preInfo 和 postInfo 两个参数中做计算得出。

动画处理

前置基础有点多😂😂😂,不过通过对上面几个类有一些了解,下面在分析动画触发、信息处理时就不用反复解释一些变量的意义了。

dispatchLayoutStep3

在之前分析布局阶段的博客中提到 dispatchLayoutStep1、2、3 三个核心方法,分别对应三种状态 STEP_START、STEP_LAYOUT、STEP_ANIMATIONS;

很显然,STEP_ANIMATIONS 是执行动画的阶段,再来看一下 dispatchLayoutStep3 方法中对 item 动画进行了哪些操作:

RecyclerView.java

private void dispatchLayoutStep3() {
   // ...
   if (mState.mRunSimpleAnimations) { // 需要做动画
       // 倒着循环 因为可能会发生移除
       for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
           // 获取到 holder
           ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
           if (holder.shouldIgnore()) { // 如果被标记忽略则跳过
               continue;
           }
           // 获取 holder 的 key 一般情况获取的就是 position
           long key = getChangedHolderKey(holder);
           // 前置基础中 提到的 ItemHolderInfo
           // recordPostLayoutInformation 内部构建了一个 ItemHolderInfo 并且调用了 setFrom 设置了 位置信息
           final ItemHolderInfo animationInfo = mItemAnimator.recordPostLayoutInformation(mState, holder);
           // 从 ViewInfoStore 的 mOldChangedHolders 中获取 vh
           ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
           if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
               // 是否正在执行消失动画
               final boolean oldDisappearing = mViewInfoStore.isDisappearing(oldChangeViewHolder);
               final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
               // 这个 if 判断 将要发生更新动画的 vh 已经在执行消失动画
               if (oldDisappearing && oldChangeViewHolder == holder) {
                   // 用消失动画代替
                   mViewInfoStore.addToPostLayout(holder, animationInfo);
               } else {
                   // 获取 预布局阶段的 ItemHolderInfo
                   final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(oldChangeViewHolder);
                   // 设置 真正布局阶段的 ItemHolderInfo
                   mViewInfoStore.addToPostLayout(holder, animationInfo);
                   // 然后在取出来
                   ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
                   if (preInfo == null) {
                       handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
                   } else { // 用上面取出来的 preInfo postInfo 做更新动画
                       animateChange(oldChangeViewHolder, holder, preInfo, postInfo,oldDisappearing, newDisappearing);
                   }
               }
           } else { // 没有获取到 直接设置 hodler 真正布局阶段的 位置信息 并且设置 flag
               mViewInfoStore.addToPostLayout(holder, animationInfo);
           }
       }
       // 开始执行动画
       mViewInfoStore.process(mViewInfoProcessCallback);
   }
   // ...
}

代码中的注释写的比较详细,主要注意一下 mViewInfoStore.addToPostLayout 会给 ViewHolder 生成 InfoRecord 对象,并且设置 postInfo,并且给 flags 添加 FLAG_POST,然后以 <ViewHolder, InfoRecord> 键值对形式添加到 ViewInfoStore 的 mLayoutHolderMap 中;

dispatchLayoutStep1

其实上一小节 dispatchLayoutStep3 方法中也包含对动画信息的处理,也就是针对真正布局后的位置信息设置的相关代码。那么删除、新增的动画在哪里实现呢?首先,回顾一下之前分析的布局流程,真正的布局发生在 dispatchLayoutStep2 中,预布局发生在 dispatchLayoutStep1 中,结合之前对预布局的简单解释,不难理解出预布局时肯定也对动画信息进行了处理,那么直接看一下 dispatchLayoutStep1 的相关源码,这部分需要分成两段来分析,先看第一段:

RecyclerView.java

private void dispatchLayoutStep1() {
   // ...
   if (mState.mRunSimpleAnimations) {
       // 遍历 child
       int count = mChildHelper.getChildCount();
       for (int i = 0; i < count; ++i) {
           // 获取 vh
           final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
           // 忽略、无效的 跳过
           if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
               continue;
           }
           // 构造出 ItemHolderInfo
           final ItemHolderInfo animationInfo = mItemAnimator
                   .recordPreLayoutInformation(mState, holder,
                           ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                           holder.getUnmodifiedPayloads());
           // 注意这里 时 addToPreLayout. 表示预布局阶段
           // 此时设置的是 InfoRecord 的 preInfo,flag 是 FLAG_PRE
           mViewInfoStore.addToPreLayout(holder, animationInfo);
           // 如果 holder 发生改变 添加到 ViewInfoStore 的 mOldChangedHolders 中
           if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                   && !holder.shouldIgnore() && !holder.isInvalid()) {
               long key = getChangedHolderKey(holder); // 获取 key 一般是 position
               mViewInfoStore.addToOldChangeHolders(key, holder);
           }
       }
   }
   // ...
}

这一段也不复杂,记录当前 holder 预布局阶段的位置信息(InfoRecord 的 preInfo)到 ViewInfoStore 的 mLayoutHolderMap 中,且添加了 FLAG_PRE 到 flags 中;

并且如果 holder 发生改变就添加到 ViewInfoStore 的 mOldChangedHolders 中;

再看下面的代码:

RecyclerView.java

private void dispatchLayoutStep1() {
   // ...
   if (mState.mRunPredictiveAnimations) {
       // ...
       // 这次是预布局 计算可用空间时忽略了要删除的项目 所以如果发生删除 会有新的 item 添加进去
       mLayout.onLayoutChildren(mRecycler, mState);
       // ...
       // 遍历 child
       for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
           final View child = mChildHelper.getChildAt(i);
           final ViewHolder viewHolder = getChildViewHolderInt(child);
           if (viewHolder.shouldIgnore()) {
               continue;
           }
           // 这个判断也就是没有经历过上一部分代码的 vh (onLayoutChildren 中新加入的 item)
           // InfoRecord 为 null 或者 flags 不包含 FLAG_PRE
           if (!mViewInfoStore.isInPreLayout(viewHolder)) {
               int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
               // 判断是否是隐藏的
               boolean wasHidden = viewHolder
                       .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
               if (!wasHidden) { // 没有隐藏 则标记在预布局阶段出现
                   flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
               }
               // 构造出 ItemHolderInfo
               final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                       mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
               if (wasHidden) {
                   // 隐藏的 如果发生更新 并且没有被移除 就添加到 mOldChangedHolders
                   // 设置 preInfo 设置 flag为 FLAG_PRE
                   recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
               } else { // 没有隐藏的 设置 flag FLAG_APPEAR, 并且设置 preInfo
                   mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
               }
           }
       }
       clearOldPositions();
   } else {
       clearOldPositions();
   }
   // ...
}

这里结合之前解释预布局时的图来理解下:

Android 源码浅析RecyclerView ItemAnimator

第一部分执行时,item1、2、3 都会执行 addToPreLayout,addToPreLayout 会生成 InfoRecord 并且设置其 preInfo 存储 vh 的位置信息,然后以 <ViewHolder, InfoRecord> 键值对形式添加到 ViewInfoStore 的 mLayoutHolderMap 中;

然后第二部分执行了 onLayoutChildren 进行了预布局,以 LinearLayoutManager 为例,在计算可用空间时会忽略要删除的 item3,从而 item4 被添加到 RecyclerView 中,再次对 child 进行遍历时进行 mViewInfoStore.isInPreLayout(viewHolder) 判断时显然 item4 对应的 ViewHolder 在 mLayoutHolderMap 中获取为 null,那么就能知道 item4 属于新增出来的,就在最后调用 mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); 生成 InfoRecord 设置位置信息,并且添加 flag 为 FLAG_APPEAR 添加到 mLayoutHolderMap 中。

来源:https://juejin.cn/post/7169857225949708301

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com