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地址


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

人生得意须尽欢, 桃花坞里桃花庵
点个关注呗,对,不信你点试试?

推荐阅读更多精彩内容