亲宝软件园·资讯

展开

Android 源码浅析RecyclerView ItemAnimator

孙先森Blog 人气:0

前言

在这个系列博客的第二篇的最后部分提到了预布局,在预布局阶段,计算剩余空间时会把将要移除的 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 两个参数中做计算得出。

动画处理

前置基础有点多

加载全部内容

相关教程
猜你喜欢
用户评论