美文网首页Android UIAndroid开发程序员
快速定制简单的Item Animation

快速定制简单的Item Animation

作者: lileibuaa | 来源:发表于2016-03-24 22:47 被阅读1347次

    Item Animation的基本原理

    我们都知道,给RecyclerView添加Item Animation的方法是setItemAnimator(ItemAnimator animator)。因此我们可以以抽象类ItemAnimator为切入点,看看Recyclerview的Item Animation是怎样实现的。

    首先列举一下ItemAnimator中几个比较重要的方法签名:

      • public ItemHolderInfo recordPreLayoutInformation(State state,ViewHolder viewHolder,int changeFlags,List<Object> payloads)
      • public ItemHolderInfo recordPostLayoutInformation(State state, ViewHolder viewHolder)

      这两个方法是成对出现的,分别用于记录同一个ViewHolder在layout执行前后view位置等信息,分别对应了动画开始前的view状态和动画结束后的view状态。

      • public abstract boolean animateDisappearance(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)
      • public abstract boolean animateAppearance(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)
      • public abstract boolean animatePersistence(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)
      • public abstract boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)

      这四个方法都是由RecyclerView调用,调用时机分别为在执行layout过程之后viewHolderRecyclerView中移除(从有到无)、出现(从无到有)、不变(同一个viewHolder但是尺寸、位置可能发生变化)、改变(从oldHolder变为newHolderviewHolder发生了变化)。

    这四个方法中都包含preLayoutInfopostLayoutInfo,这两个参数就是由前面recordPreLayoutInformationrecordPostLayoutInformation方法计算出来的viewHolder信息。我们就可以根据viewHolder的初态(preLayoutInfo)和终态(postLayoutInfo)实现自定义的动画。但是动画并不是在这个方法中启动,在这四个方法中我们仅仅是根据preLayoutInfopostLayoutInfo判断是否需要动画和需要什么动画。如果不需要执行动画,就必须在这四个方法中调用dispatchAnimationFinished(ViewHolder)方法并且最后的return值必须为false;反之如果需要执行动画return值就必须为true,之后系统就会调用runPendingAnimations()方法,并在runPendingAnimations()中启动各种动画,在动画执行完毕后也必须要调用dispatchAnimationFinished(ViewHolder)方法。

    1. abstract public void runPendingAnimations()

    如果上面四个方法中有一个的返回值是true,在下一帧的时候就会执行此方法,所有的动画都可以在此方法中启动。

      • abstract public void endAnimation(ViewHolder item)
      • abstract public void endAnimations()
        结束动画,并确保各个viewHolder都处于最终态。

    到此为止,我们可以缕一下整个过程。

    首先RecyclerView在执行layout前后会调用recordPreLayoutInformationrecordPostLayoutInformation方法,并保存layout前后viewHodler的位置等信息;然后根据layout的情况执行animateXXX方法,我们在animateXXX方法中决定是否执行动画和执行何种动画;如果需要执行动画,则可以在runPendingAnimations方法中启动动画;最后在endAnimationendAnimations方法中确保viewHolder处于最终态。

    快速实现自定义Item Animation

    问当今如何code最快,那必然是ctrl-C加上ctrl-V。(开个玩笑~~~)

    不过现在讲的是如何快速的自定义Item Animation,基于学习的目的,那当然还是需要copy一些代码的嘛。至于以后项目中需要或者兴趣使然要深入自定义Item Animation就需要自己参照原有实现进行封装了。

    废话不多说,下面进入正题!!!

    android support v7包里已经包含了一个Item Animation的默认实现:android.support.v7.widget.DefaultItemAnimatorDefaultItemAnimator继承了SimpleItemAnimator,而SimpleItemAnimator又继承了RecyclerView.ItemAnimatorDefaultItemAnimator实现了在添加、移除item时View的透明度发生变化,在移动item时view的位置发生变化。我们要做的仅仅是对DefaultItemAnimator进行局部的修改~~~

    recordPreLayoutInformation和recordPostLayoutInformationItemAnimator中已经实现,animateDisappearanceanimateAppearanceanimatePersistenceanimateChangeSimpleItemAnimator中也已实现,runPendingAnimationsDefaultItemAnimator中已经实现,我们用这些默认实现就可以了。以添加Item动画为例,我们只要修改如下3个地方就可以实现Item旋转进入动画(仅列出部分关键代码,注释为原代码,////中间为修改后的代码):

    1. 设置动画的初始值,将view设置为旋转180度

      @Override
      public boolean animateAdd(final ViewHolder holder) {
          resetAnimation(holder);
          /**ViewCompat.setAlpha(holder.itemView, 0);**/
          //
          ViewCompat.setRotation(holder.itemView, 180);
          //
          mPendingAdditions.add(holder);
          return true;
      }
      
    
    2. 启动动画,让view再转180度。并在动画取消时将view设置为终态,也就是旋转0度
    
        ```
        private void animateAddImpl(final RecyclerView.ViewHolder holder) {
            ...
            /**animation.alpha(1)**/
            //
            animation.rotationBy(180)
            //
                    .setDuration(getAddDuration()).
                    setListener(new VpaListenerAdapter() {
                        ...
                        @Override
                        public void onAnimationCancel(View view) {
                            /**ViewCompat.setAlpha(view, 1);**/
                            //
                            ViewCompat.setRotation(view, 0);
                            //
                        }
                        ...
                    }).start();
        }
    
    1. 结束动画,确保view在动画结束或者取消后能回到终态,也就是旋转0度。(这里省略了endAnimations()方法的修改)

      @Override
      public void endAnimation(RecyclerView.ViewHolder item) {
          ...
          if (mPendingAdditions.remove(item)) {
              /**ViewCompat.setAlpha(view, 1);**/
              //
              ViewCompat.setRotation(view, 0);
              //
              dispatchAddFinished(item);
          }
          ...
          for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
              ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
              if (additions.remove(item)) {
                  /**ViewCompat.setAlpha(view, 1);**/
                  //
                  ViewCompat.setRotation(view, 0);
                  //
                  dispatchAddFinished(item);
                  if (additions.isEmpty()) {
                      mAdditionsList.remove(i);
                  }
              }
          }
          ...
      }
      
    
    通过以上3步,我们就是实现了Item旋转进入的动画,以下是演示效果。
    
    ![Demo 演示效果](https://img.haomeiwen.com/i289461/7b20ae37379c1199.gif?imageMogr2/auto-orient/strip)
    
    >[Demo地址](https://github.com/lileibuaa/CustomItemAnimator)
    
    **再次强调,以上步骤仅适用于学习。在项目中使用还需自己参照实现自己想要的效果。**
    
    PS:顺带说下如何解决调用`notifyDataSetChanged`后,没有触发Item Animation的问题。。
    
    要想在调用`notifyDataSetChanged`之后触发Item Animation,必须要在自定义adapter中调用方法`setHasStableIds(true)`;并实现`public long getItemId(int position)`方法返回各Item的id。。至于为啥,留待以后再探究吧。。。请原谅我这个拖延症重度患者。

    相关文章

      网友评论

        本文标题:快速定制简单的Item Animation

        本文链接:https://www.haomeiwen.com/subject/wqwrlttx.html