Android控件——RecyclerView系列四(Item Animator)

1. Item Animator

在实际开发中,我们想要让自己的界面变得更加酷炫,免不了加入动画这个元素,而RecyclerView能够通过setItemAnimator(ItemAnimator animator)设置添加、删除、移动、改变的动画效果。RecyclerView提供了默认的ItemAnimator实现类:DefaultItemAnimator。

1.1 DefaultAnimator分析

DefaultItemAnimator继承自SimpleItemAnimator,SimpleItemAnimator继承自ItemAnimator。

首先,来看一下ItemAnimator类的几个重要方法:

  • animateAppearance():当ViewHolder出现在屏幕上时被调用(可能是add或move)
  • animateDisappearance(): 当ViewHolder消失在屏幕上时被调用(可能是remove或move)
  • animatePersistence(): 在没调用notifyItemChanged()和notifyDataSetChanged()的情况下布局发生改变时被调用。
  • animateChange(): 在显式调用notifyItemChanged()或notifyDataSetChanged()时被调用。
  • runPendingAnimations(): RecyclerView动画的执行方式并不是立即执行,而是每帧执行一次,比如两帧之间添加了多个Item,则会将这些将要执行的动画Pending住,保存在成员变量中,等到下一帧一起执行。该方法执行的前提是前面animateXxx()返回true。
  • isRunning(): 是否有动画要执行或正在执行
  • dispatchAnimationsFinished(): 当全部动画执行完毕时被调用

上面的几个方法可能不容易懂,Android提供了SimpleItemAnimator类,该类提供了一系列更易懂的API

  • animateAdd(ViewHolder holder): 当Item添加时被调用。
  • animateMove(ViewHolder holder, int fromX, int fromY, int toX, int toY): 当Item移动时被调用。
  • animateRemove(ViewHolder holder): 当Item删除时被调用。
  • animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop): 当显式调用notifyItemChanged()或notifyDataSetChanged()时被调用。

对于以上四个方法,注意两点:

  • 当Xxx动画开始执行前(在runPendingAnimations()中)需要调用dispatchXxxStarting(holder),执行完后需要调用dispatchXxxFinished(holder)
  • 这些方法的内部实际上并不是书写执行动画的代码,而是将需要执行动画的Item全部存入成员变量中,并且返回值为true,然后在runPendingAnimations()中一并执行。

现在我们看一下DefaultItemAnimator的成员变量:

private ArrayList<ViewHolder> mPendingRemovals = new ArrayList();
    private ArrayList<ViewHolder> mPendingAdditions = new ArrayList();	//存放下一帧要执行一系列add动画
    private ArrayList<DefaultItemAnimator.MoveInfo> mPendingMoves = new ArrayList();
    private ArrayList<DefaultItemAnimator.ChangeInfo> mPendingChanges = new ArrayList();
    ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList();	//存放正在执行的一批add动画
    ArrayList<ArrayList<DefaultItemAnimator.MoveInfo>> mMovesList = new ArrayList();
    ArrayList<ArrayList<DefaultItemAnimator.ChangeInfo>> mChangesList = new ArrayList();
    ArrayList<ViewHolder> mAddAnimations = new ArrayList();	//存放当前正在执行的add动画
    ArrayList<ViewHolder> mMoveAnimations = new ArrayList();
    ArrayList<ViewHolder> mRemoveAnimations = new ArrayList();
    ArrayList<ViewHolder> mChangeAnimations = new ArrayList();

DefaultItemAnimator实现了SimpleItemAnimator的animateAdd()方法,该方法只是将该item添加到mPendingAdditions中,等到runPendingAnimations()中执行。

public boolean animateAdd(ViewHolder holder) {
        this.resetAnimation(holder);	//重置清空所有动画
        holder.itemView.setAlpha(0.0F);	//将要做动画的View先变成透明
        this.mPendingAdditions.add(holder);
        return true;
    }

接着看runPendingAnimations()的实现,该方法是执行remove,move,change,add动画,执行顺序为:remove动画最先执行,随后move和change并行执行,最后是add动画。为了简化,我们将remove,move,change动画执行过程省略,只看执行add动画的过程,如下:

public void runPendingAnimations() {
	//1、判断是否有动画要执行,即各个动画的成员变量里是否有值。
     //2、执行remove动画
     //3、执行move动画
     //4、执行change动画,与move动画并行执行
     //5、执行add动画
     if (additionsPending) {
                additions = new ArrayList();
                additions.addAll(this.mPendingAdditions);
                this.mAdditionsList.add(additions);
                this.mPendingAdditions.clear();
                adder = new Runnable() {
                    public void run() {
                        Iterator var1 = additions.iterator();

                        while(var1.hasNext()) {
                            ViewHolder holder = (ViewHolder)var1.next();
                            DefaultItemAnimator.this.animateAddImpl(holder);	//执行动画的方法
                        }

                        additions.clear();
                        DefaultItemAnimator.this.mAdditionsList.remove(additions);
                    }
                };
                if (!removalsPending && !movesPending && !changesPending) {
                    adder.run();
                } else {
                    long removeDuration = removalsPending ? this.getRemoveDuration() : 0L;
                    long moveDuration = movesPending ? this.getMoveDuration() : 0L;
                    long changeDuration = changesPending ? this.getChangeDuration() : 0L;
                    long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
                    View view = ((ViewHolder)additions.get(0)).itemView;
                    ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
                }
            }
}

为了防止在执行add动画时外面有新的add动画添加到mPendingAdditions中,从而导致执行add动画错乱,这里将mPendingAdditions的内容移动到局部变量additions中,然后遍历additions执行动画。

在runPendingAnimations()中,animateAddImpl()是执行add动画的具体方法,其实就是将itemView的透明度从0变到1(在animateAdd()中已经将view的透明度变为0),实现如下:

void animateAddImpl(final ViewHolder holder) {
        final View view = holder.itemView;
        final ViewPropertyAnimator animation = view.animate();
        this.mAddAnimations.add(holder);
        animation.alpha(1.0F).setDuration(this.getAddDuration()).setListener(new AnimatorListenerAdapter() {
            public void onAnimationStart(Animator animator) {
                DefaultItemAnimator.this.dispatchAddStarting(holder);	//在开始add动画前调用
            }

            public void onAnimationCancel(Animator animator) {
                view.setAlpha(1.0F);
            }

            public void onAnimationEnd(Animator animator) {
                animation.setListener((AnimatorListener)null);
                DefaultItemAnimator.this.dispatchAddFinished(holder);	//在结束add动画后调用
                DefaultItemAnimator.this.mAddAnimations.remove(holder);
                DefaultItemAnimator.this.dispatchFinishedWhenDone();	//结束所有动画后调用
            }
        }).start();
    }

1.2 自定义ItemAnimator

从DefaultItemAnimator类的实现来看,发现自定义Item Animator较为麻烦,需要继承SimpleItemAnimator类,然后实现一堆方法。可以利用第三方开源库,例如:recyclerview-animators

recyclerview-animators提供了一系列的Animator,比如FadeInAnimator,ScaleInAnimator。其次,如果该库中没有你满意的动画,该库提供了BaseItemAnimator类,该类继承自SimpleItemAnimator,进一步封装了自定义Item Animator的代码,使得自定义Item Animator更方便,你只需要关注动画本身。如果要实现DefaultItemAnimator的代码,只需要以下实现:

public class DefaultItemAnimator extends BaseItemAnimator {

   public DefaultItemAnimator() {
   }

   public DefaultItemAnimator(Interpolator interpolator) {
     mInterpolator = interpolator;
   }

   @Override protected void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
     ViewCompat.animate(holder.itemView)
         .alpha(0)
         .setDuration(getRemoveDuration())
         .setListener(new DefaultRemoveVpaListener(holder))
         .setStartDelay(getRemoveDelay(holder))
         .start();
   }

   @Override protected void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
     ViewCompat.setAlpha(holder.itemView, 0); //透明度先变为0
   }

   @Override protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
     ViewCompat.animate(holder.itemView)
         .alpha(1)
         .setDuration(getAddDuration())
         .setListener(new DefaultAddVpaListener(holder))
         .setStartDelay(getAddDelay(holder))
         .start();
   }
 }

对于RecyclerView的Item Animator,有一个常见的坑就是”闪屏问题”。这个问题的描述是:当Item视图中有图片和文字,当更新文字并调用notifyItemChanged()时,文字改变的同时图片会闪一下。这个问题的原因是当调用notifyItemChanged()时,会调用DefaultItemAnimator的animateChangeImpl()执行change动画,该动画会使得Item的透明度从0变为1,从而造成闪屏。

解决办法很简单,在rv.setAdapter()之前调用((SimpleItemAnimator)rv.getItemAnimator()).setSupportsChangeAnimations(false)禁用change动画。

你可能感兴趣的:(Android基础,Android视图动效)