我们经常会遇到在一个list中删除一条数据,这时候一般会有一个飞出的动画效果,如下图:
在RecyclerView中可以通过setItemAnimator函数设置一个ItemAnimator,实现item的add、remove、change等动作的动效。下面我们就通过ItemAnimator来实现上面的效果。
首先创建一个类,继承至Simple
ItemAnimator,如下:
class FlyAnimator extends SimpleItemAnimator{ @Override public boolean animateRemove(RecyclerView.ViewHolder holder) { return false; } @Override public boolean animateAdd(RecyclerView.ViewHolder holder) { return false; } @Override public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { return false; } @Override public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) { return false; } @Override public void runPendingAnimations() { } @Override public void endAnimation(RecyclerView.ViewHolder item) { } @Override public void endAnimations() { } @Override public boolean isRunning() { return false; } }
Simple
ItemAnimator是一个抽象类,需要实现几个函数。
因为我们要实现是一个remove的动作,需要在animateRemove中处理。这里我们参照DefaultItemAnimator的做法,首先需要两个list,然后在
animateRemove将holder添加进list中,这里暂时不做处理,如下:
List<RecyclerView.ViewHolder> removeHolders = new ArrayList<>(); List<RecyclerView.ViewHolder> removeAnimators = new ArrayList<>(); @Override public boolean animateRemove(RecyclerView.ViewHolder holder) { removeHolders.add(holder); return true; }
至于另外一个list下面会用到。
既然我们在animateRemove函数中不做动效处理,那么应该在哪里处理?
答案是在runPedingAnimations中来处理,代码如下:
@Override public void runPendingAnimations() { if(!removeHolders.isEmpty()) { for(RecyclerView.ViewHolder holder : removeHolders) { remove(holder); } removeHolders.clear(); } }
遍历removeHolders,依次执行remove,这个函数是自定义的,用于执行动画,代码如下:
private void remove(final RecyclerView.ViewHolder holder){ removeAnimators.add(holder); TranslateAnimation animation = new TranslateAnimation(0, 1000, 0, 0); animation.setDuration(500); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { dispatchRemoveStarting(holder); } @Override public void onAnimationEnd(Animation animation) { removeAnimators.remove(holder); dispatchRemoveFinished(holder); if(!isRunning()){ dispatchAnimationsFinished(); } } @Override public void onAnimationRepeat(Animation animation) { } }); holder.itemView.startAnimation(animation); }
可以看到就是对holder的itemview执行来一个移动动画。
这里使用removeAnimators来管理所有的remove动画,目前是判断所有的remove动画是否结束,这个判断在isRunning函数中,代码如下:
@Override public boolean isRunning() { return !(removeHolders.isEmpty() && removeAnimators.isEmpty()); }
当两个list都为空的时候,所有动画都完成了,回到remove代码中这时候执行disPatchAnimationsFinished函数。
通过上面几步,实现了remove的动效,当我们执行的时候发现确实有了飞出的效果,但是下面的item却瞬间上移导致重叠。效果如下:
这时因为我们目前只定义了remove的效果,实际上不仅有飞出的动作还有一个上移的动作,所以还需要定义一下move的效果,同remove一样需要两个list,在animateMove函数中将holder添加至list中,如下:
List<RecyclerView.ViewHolder> moveHolders = new ArrayList<>(); List<RecyclerView.ViewHolder> moveAnimators = new ArrayList<>(); @Override public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { holder.itemView.setTranslationY(fromY - toY); moveHolders.add(holder); return true; }
注意在remove的一瞬间,下方的item实际上就已经上移了,所以在animateMove中设置item的translationY使其保持在未上移的位置。
然后同样在
runPedingAnimations中处理,这时
runPedingAnimations代码如下:
@Override public void runPendingAnimations() { if(!removeHolders.isEmpty()) { for(RecyclerView.ViewHolder holder : removeHolders) { remove(holder); } removeHolders.clear(); } if(!moveHolders.isEmpty()){ for(RecyclerView.ViewHolder holder : moveHolders) { move(holder); } moveHolders.clear(); } }
这里move同样是自定义的一个函数,代码如下:
private void move(final MoveInfo moveInfo){ moveAnimators.add(moveInfo); ObjectAnimator animator = ObjectAnimator.ofFloat(moveInfo.holder.itemView, "translationY", moveInfo.holder.itemView.getTranslationY(), 0); animator.setDuration(500); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(android.animation.Animator animation) { dispatchMoveStarting(moveInfo.holder); } @Override public void onAnimationEnd(android.animation.Animator animation) { dispatchMoveFinished(moveInfo.holder); moveAnimators.remove(moveInfo.holder); if(!isRunning()) dispatchAnimationsFinished(); } }); animator.start(); }
执行了一个属性动画,修改了item的translationY使其上移回原位置。同时注意修改isRunning函数,如下:
@Override public boolean isRunning() { return !(removeHolders.isEmpty() && removeAnimators.isEmpty() && moveHolders.isEmpty() && moveAnimators.isEmpty()); }
这样就实现了一开始的飞出效果。
总结一下,其实自定义
ItemAnimator比较简单,虽然代码接近百行,但其实主要就是执行动画。需要注意的就是有些情况需要配合move的动作。
自定义
ItemAnimator后,直接为RecyclerView设置即可:
list.setItemAnimator(new FlyAnimator());
设置后如果调用了adapter的notifyItemRemoved函数就会执行remove的动效。
完整代码如下:
class FlyAnimator extends SimpleItemAnimator{ List<RecyclerView.ViewHolder> removeHolders = new ArrayList<>(); List<RecyclerView.ViewHolder> removeAnimators = new ArrayList<>(); List<RecyclerView.ViewHolder> moveHolders = new ArrayList<>(); List<RecyclerView.ViewHolder> moveAnimators = new ArrayList<>(); @Override public boolean animateRemove(RecyclerView.ViewHolder holder) { removeHolders.add(holder); return true; } @Override public boolean animateAdd(RecyclerView.ViewHolder holder) { return false; } @Override public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { holder.itemView.setTranslationY(fromY - toY); moveHolders.add(holder); return true; } @Override public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) { return false; } @Override public void runPendingAnimations() { if(!removeHolders.isEmpty()) { for(RecyclerView.ViewHolder holder : removeHolders) { remove(holder); } removeHolders.clear(); } if(!moveHolders.isEmpty()){ for(RecyclerView.ViewHolder holder : moveHolders) { move(holder); } moveHolders.clear(); } } @Override public void endAnimation(RecyclerView.ViewHolder item) { } @Override public void endAnimations() { } @Override public boolean isRunning() { return !(removeHolders.isEmpty() && removeAnimators.isEmpty() && moveHolders.isEmpty() && moveAnimators.isEmpty()); } private void remove(final RecyclerView.ViewHolder holder){ removeAnimators.add(holder); TranslateAnimation animation = new TranslateAnimation(0, 1000, 0, 0); animation.setDuration(500); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { dispatchRemoveStarting(holder); } @Override public void onAnimationEnd(Animation animation) { removeAnimators.remove(holder); dispatchRemoveFinished(holder); if(!isRunning()){ dispatchAnimationsFinished(); } } @Override public void onAnimationRepeat(Animation animation) { } }); holder.itemView.startAnimation(animation); } private void move(final RecyclerView.ViewHolder holder){ moveAnimators.add(holder); ObjectAnimator animator = ObjectAnimator.ofFloat(holder.itemView, "translationY", holder.itemView.getTranslationY(), 0); animator.setDuration(500); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(android.animation.Animator animation) { dispatchMoveStarting(holder); } @Override public void onAnimationEnd(android.animation.Animator animation) { dispatchMoveFinished(holder); moveAnimators.remove(holder); if(!isRunning()) dispatchAnimationsFinished(); } }); animator.start(); } }