RecyclerView(6)-自定义ItemAnimator

自定义ItemAnimtor不算太难,更何况还有一个官方的默认动画类DefaultItemAnimator可以参考,相信很多同学都是会的,会自定义ItemAnimtor的同学可以略过本篇,今天不扯淡了 直接进入主题!

目录如下:
1、 一些基础概念
2、 源码简单分析
· 2.1、以notifyItemInserted为入口梳理RecyclerView中Animation流程
· 2.2、 DefaultItemAnimtor解读
3、 自定义RecyclerView.ItemAnimator
· 3.1 先生骨架
· 3.2 再长血肉

1、一些基础概念

官方有一个默认Item动画类DafaultItemAnimator, 其中 DefaultItemAnimator继承了SimpleItemAnimator , 在继承了RecyclerView.ItemAnimator
1、SimpleItemAnimator 它是一个包装类,用来判断当前的ViewHolder到底是执行移动、移除、添加或者改变等行为。

2、DefaultItemAnimator 是执行具体动画类,它负责将viewHolder初始化、保存需要执行动画的ViveHolder以及动画信息、执行具体的动画。 我们自定义ItemAnimtor 也是仿照这个来的。

3、 其中DefaultItemAnimatoranimate + 动作 为名的的方法(比如animateAdd())做的事情有:计算动画信息,保存动画信息,初始化view的状态,且可以控制该VIewHolder是否执行该次动画,如果返回值为false那么那么不会执行该ViewHolder的改次动画;

4、 DefaultItemAnimatoranimate + 动作 + Impl 为名的方法,做的动作是执行具体的动画动作。

5、runPendingAnimations是最终执行具体动画的方法

2、 源码简单分析

2.1 以notifyItemInserted为入口梳理RecyclerView中Animation流程

notifyitemInserted()为入口分析 触发一个itemView的动画都经历了哪些类哪些方法;ps:其它 notifyitemitem...等方法流程与之类似

1、触发notifyitemInserted方法;
2、调用mObservable.notifyItemRangeInserted被观察者方法;
3、遍历所注册的观察者RecyclerViewDataObserver 并调用其 onItemRangeInserted()方法;
4 & 5、添加动画标识UpdateOp并保存在队列中
7 & 8、执行 mUpdateChildViewsRunnable : RunnableRun方法,调用 consumePendingUpdateOperations方法
10&11&12&13&14&15&16 调用 这里做了一些列动画基础数据的配置与存储
17、调用 dispatchLayout方法,先会调用layoutManager的布局view 添加view;后在调用 dispatchLayoutStep3方法
18、dispatchLayoutStep3()该方法是这样解释的

 * The final step of the layout where we save the information about views for animations,
     * trigger animations and do any necessary cleanup.

最终在布局完成后,会触发item的动画
19-end 、调用ViewInfoStore.ProcessCallback.processPersistent方法,其中内部调用到了 ItemAnimtoranimateChange 方法 ,在去调用 ItemAnimtor . runPendingAnimations执行实现类设置的动画。

一个简单的 触发Item动画 时序过程就是如此了。 可以看到我们最终执行动画都是在 RecyclerView中的dispatchLayoutStep3执行的。执行具体动画在 ItemAnimtor类的runPendingAnimations方法

2.2 DefaultItemAnimtor解读

这边着重得来分析一下 DefaultItemAnimtorrunPendingAnimations方法,因为该方法最终执行动画

   @Override
   public void runPendingAnimations() {
 //  判断是否 remove、move、change、add 列表是否为空
       boolean removalsPending = !mPendingRemovals.isEmpty();
       boolean movesPending = !mPendingMoves.isEmpty();
       boolean changesPending = !mPendingChanges.isEmpty();
       boolean additionsPending = !mPendingAdditions.isEmpty();
       if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
           return;
       }
       // First, remove stuff  先执行 remove列表的动画
       for (ViewHolder holder : mPendingRemovals) {
           animateRemoveImpl(holder);
       }
// 清空存储
       mPendingRemovals.clear();
       // Next, move stuff  执行move动画
       if (movesPending) {
           final ArrayList<MoveInfo> moves = new ArrayList<>();
           moves.addAll(mPendingMoves);
           mMovesList.add(moves);
           mPendingMoves.clear();
           Runnable mover = new Runnable() {
               @Override
               public void run() {
                   for (MoveInfo moveInfo : moves) {
                       animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
                               moveInfo.toX, moveInfo.toY);
                   }
                   moves.clear();
                   mMovesList.remove(moves);
               }
           };
           if (removalsPending) {
               View view = moves.get(0).holder.itemView;
               ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
           } else {
               mover.run();
           }
       }
       // Next, change stuff, to run in parallel with move animations   执行vhange动画
       if (changesPending) {
           final ArrayList<ChangeInfo> changes = new ArrayList<>();
           changes.addAll(mPendingChanges);
           mChangesList.add(changes);
           mPendingChanges.clear();
           Runnable changer = new Runnable() {
               @Override
               public void run() {
                   for (ChangeInfo change : changes) {
                       animateChangeImpl(change);
                   }
                   changes.clear();
                   mChangesList.remove(changes);
               }
           };
           if (removalsPending) {
               ViewHolder holder = changes.get(0).oldHolder;
               ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
           } else {
               changer.run();
           }
       }
       // Next, add stuff   执行添加动画
       if (additionsPending) {
           final ArrayList<ViewHolder> additions = new ArrayList<>();
           additions.addAll(mPendingAdditions);
           mAdditionsList.add(additions);
           mPendingAdditions.clear();
           Runnable adder = new Runnable() {
               @Override
               public void run() {
                   for (ViewHolder holder : additions) {
                       animateAddImpl(holder);
                   }
                   additions.clear();
                   mAdditionsList.remove(additions);
               }
           };
           if (removalsPending || movesPending || changesPending) {
               long removeDuration = removalsPending ? getRemoveDuration() : 0;
               long moveDuration = movesPending ? getMoveDuration() : 0;
               long changeDuration = changesPending ? getChangeDuration() : 0;
               long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
               View view = additions.get(0).itemView;
               ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
           } else {
               adder.run();
           }
       }
   }

通过以上信息可以了解到:runPendingAnimations最终执行动画操作,
其中animate + 动作 + Impl 为名的方法,做具体的动画实现。

3、 自定义RecyclerView.ItemAnimator

3.1 先生骨架

我们要自定义一个BaseItemAnimator ,类图如下所示

其中BaseItemAnimator仿照DefaultItemAnimator 的一个结构,唯一不同的是 执行具体动画代码被抽象为了方法。

FadeItemAnimatorRotateItemAnimator 等继承BaseItemAnimator 是具体动画的实现类
这边仿照 DefaultItemAnimator 搭建据图框架Base框架

public class BaseItemAnimator extends SimpleItemAnimator {
    //业务控制是否执行该viewHolder的动画  比如通讯录列表,判断只有联
    //系人的ViewHolder执行动画,如果是分组头部ViewHolder则不执行动画
    @Override
    public boolean animateRemove(RecyclerView.ViewHolder holder) {
        // 计算 holder的动画参数 如:x偏移量 y轴偏移量 透明度等等
        // 保存 viewHolder 以及 动画参数
        //  业务控制是否执行该viewHolder的动画  
        //      比如通讯录列表,判断只有联系人的ViewHolder执行动画,
        //      如果是分组头部ViewHolder则不执行动画
        return false;
    }

    @Override
    public boolean animateAdd(RecyclerView.ViewHolder holder) {
        // 计算 holder的动画参数 如:x偏移量 y轴偏移量 透明度等等
        // 保存 viewHolder 以及 动画参数
        //  业务控制是否执行该viewHolder的动画  
        return false;
    }


    //用于控制添加,移动更新时,其它Item的动画执行
    @Override
    public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        // 计算 holder的动画参数 如:x偏移量 y轴偏移量 透明度等等
        // 保存 viewHolder 以及 动画参数
        //  业务控制是否执行该viewHolder的动画  
        return false;
    }

    //Item更新回调
    @Override
    public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
        // 计算 holder的动画参数 如:x偏移量 y轴偏移量 透明度等等
        // 保存 viewHolder 以及 动画参数
        //  业务控制是否执行该viewHolder的动画  
        return false;
    }

    //真正控制执行动画的地方
    @Override
    public void runPendingAnimations() {
            // 根据保存的 viewHolder 以及 animInfo   执行动画
    }

    //停止某个Item的动画
    @Override
    public void endAnimation(RecyclerView.ViewHolder item) {
          
    }

    //停止所有动画
    @Override
    public void endAnimations() {

    }

    @Override
    public boolean isRunning() {
        return false;
    }


  /**
     * 执行移除动画
     * @param holder 被移除的ViewHolder
     * @param animator 被移动的ViewHolder对应动画对象
     */
    public abstract void setRemoveAnimation(ViewHolder holder,ViewPropertyAnimatorCompat animator);
    /**
     * 执行移除动画结束,执行还原,因为该ViewHolder会被复用
     * @param holder 被移除的ViewHolder
     */
    public abstract void removeAnimationEnd(ViewHolder holder);
    /**
     * 执行添加动画初始化 这里设置透明为0添加时就会有渐变效果当然你可以在执行动画代码之前执行
     * @param holder 添加的ViewHolder
     */
    public abstract void addAnimationInit(ViewHolder holder);
    /**
     * 执行添加动画
     * @param holder 添加的ViewHolder
     * @param animator 添加的ViewHolder对应动画对象
     */
    public abstract void setAddAnimation(ViewHolder holder,ViewPropertyAnimatorCompat animator);
    /**
     * 取消添加还原状态以复用
     * @param holder 添加的ViewHolder
     */
    public abstract void addAnimationCancel(ViewHolder holder);
    /**
     * 更新时旧的ViewHolder动画
     * @param holder 旧的ViewHolder
     * @param animator ViewHolder对应动画对象
     */
    public abstract void setOldChangeAnimation(ViewHolder holder,ViewPropertyAnimatorCompat animator);
    /**
     * 更新时旧的ViewHolder动画结束,执行还原
     * @param holder
     */
    public abstract void oldChangeAnimationEnd(ViewHolder holder);
    /**
     * 更新时新的ViewHolder初始化
     * @param holder 更新时新的ViewHolder
     */
    public abstract void newChangeAnimationInit(ViewHolder holder);
    /**
     * 更新时新的ViewHolder动画
     * @param holder 新的ViewHolder
     * @param animator ViewHolder对应动画对象
     */
    public abstract void setNewChangeAnimation(ViewHolder holder,ViewPropertyAnimatorCompat animator);
    /**
     * 更新时新的ViewHolder动画结束,执行还原
     * @param holder
     */
    public abstract void newChangeAnimationEnd(ViewHolder holder);
}

3.2、再长血肉

具体实现我这边只写一个 FadeItemAnimator

public class FadeItemAnimator extends BaseItemAnimator {
    /**
     * 执行移除动画
     * @param holder 被移除的ViewHolder
     * @param animator 被移动的ViewHolder对应动画对象
     */
    @Override
    public void setRemoveAnimation(RecyclerView.ViewHolder holder, ViewPropertyAnimatorCompat animator) {
        animator.alpha(0);
    }

    /**
     * 执行移除动画结束,执行还原,因为该ViewHolder会被复用
     * @param view 被移除的ViewHolder
     */
    @Override
    public void removeAnimationEnd(RecyclerView.ViewHolder view) {
        ViewCompat.setAlpha(view.itemView,1);
    }

    /**
     * 执行添加动画初始化 这里设置透明为0添加时就会有渐变效果当然你可以在执行动画代码之前执行
     * @param holder 添加的ViewHolder
     */
    @Override
    public void addAnimationInit(RecyclerView.ViewHolder holder) {
        ViewCompat.setAlpha(holder.itemView, 0);
    }

    /**
     * 执行添加动画
     * @param holder 添加的ViewHolder
     * @param animator 添加的ViewHolder对应动画对象
     */
    @Override
    public void setAddAnimation(RecyclerView.ViewHolder holder,ViewPropertyAnimatorCompat animator) {
        animator.alpha(1);
    }

    /**
     * 取消添加还原状态以复用
     * @param holder 添加的ViewHolder
     */
    @Override
    public void addAnimationCancel(RecyclerView.ViewHolder holder) {
        ViewCompat.setAlpha(holder.itemView, 1);
    }

    /**
     * 更新时旧的ViewHolder动画
     * @param holder 旧的ViewHolder
     * @param animator ViewHolder对应动画对象
     */
    @Override
    public void setOldChangeAnimation(RecyclerView.ViewHolder holder, ViewPropertyAnimatorCompat animator) {
        animator.alpha(0);
    }

    /**
     * 更新时旧的ViewHolder动画结束,执行还原
     * @param holder
     */
    @Override
    public void oldChangeAnimationEnd(RecyclerView.ViewHolder holder) {
        ViewCompat.setAlpha(holder.itemView,1);
    }

    /**
     * 更新时新的ViewHolder初始化
     * @param holder 更新时新的ViewHolder
     */
    @Override
    public void newChangeAnimationInit(RecyclerView.ViewHolder holder) {
        ViewCompat.setAlpha(holder.itemView,0);
    }

    /**
     * 更新时新的ViewHolder动画
     * @param holder 新的ViewHolder
     * @param animator ViewHolder对应动画对象
     */
    @Override
    public void setNewChangeAnimation(RecyclerView.ViewHolder holder, ViewPropertyAnimatorCompat animator) {
        animator.alpha(1);
    }

    /**
     * 更新时新的ViewHolder动画结束,执行还原
     * @param holder
     */
    @Override
    public void newChangeAnimationEnd(RecyclerView.ViewHolder holder) {
        ViewCompat.setAlpha(holder.itemView,1);
    }
}

其他动画实现具体查看源码,也可以参考DefaultItemAnimtor 实现动画细节,当然在下面有对DefaultItemAnimtor 做一个源码分析。

源码戳我!

· RecyclerView(1)- Decoration源码解析
· RecyclerView(2)- 自定义Decoration打造时光轴效果
· RecyclerView(3)- LayoutMagager源码解析,LinearLayoutManager
· RecyclerView(4)- 核心、Recycler复用机制
· RecyclerView(5)- 自定义LayoutManager(布局、复用)
· RecyclerView(6)- 自定义ItemAnimator
· RecyclerView(7)- ItemTouchHelper
· RecyclerView(8)- MultiTypeAdapter文章MultiTypeAdapter Github地址


希望我的文章不会误导在观看的你,如果有异议的地方欢迎讨论和指正。
如果能给观看的你带来收获,那就是最好不过了。

人生得意须尽欢, 桃花坞里桃花庵
点个关注呗,对,不信你点试试?
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容