回头看看自己的博客发现RecyclerVer系列还没写完。。完全忘记了。。。来补上,本篇分为两个部分:
第一部分: DefaultItemAnimator源码解析,即Google官方提供的item动画源码
第二部分: 如何写一个自己的item动画
本篇就不贴效果图了,想看效果不妨自己去试试,我一直认为要学会怎么熟练的使用一个东西,就得了解这个东西是怎么运作的,不多说废话,正式开始:
第一部分
打开源码可以看到该类继承自SimpleItemAnimator,再点开这个类看看,发现继承自RecyclerView.ItemAnimator,看来是SimpleItemAnimator对rv的动画进行了进一步的封装,最后DefaultItemAnimator再来具体实现想要的动画,这里不详谈SimpleItemAnimator,只看Google是怎么实现rv的item动画的,说一下涉及到的几个方法的作用:
方法解读
- animateAdd(final RecyclerView.ViewHolder holder):添加item时调用的动画,用adapter.notifyItemInster()时会调用该方法,若需要定制自己的动画,需要将返回值更改为true
- animateRemove(final RecyclerView.ViewHolder holder):删除item时调用的动画,用adapter.notifyItemRemove()时会调用该方法,若需要定制自己的动画,需要将返回值更改为true
- animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY):item移动时调用的动画,再说明白点就是添加、删除item时调用的方法,参数看名字就知道啥意思,不多做解释,若需要定制自己的动画,需要将该返回值更改为true
- animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY):item接收到notifyItemChanged()或者notifyItemRangeChanged()调用的方法,同样,若需要定制自己的动画,需要将该返回值更改为true
- endAnimation(RecyclerView.ViewHolder item):动画结束时调用的方法
- canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder, List
- isRunning():判断是否有动画正在执行
runPendingAnimations():当有等待动画时调用
变量解读
private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<>();
private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<>();
private ArrayListmPendingMoves = new ArrayList<>();
private ArrayListmPendingChanges = new ArrayList<>(); ArrayList<RecyclerView.ViewHolder> mAddAnimations = new ArrayList<>();
ArrayList<RecyclerView.ViewHolder> mMoveAnimations = new ArrayList<>();
ArrayList<RecyclerView.ViewHolder> mRemoveAnimations = new ArrayList<>();
ArrayList<RecyclerView.ViewHolder> mChangeAnimations = new ArrayList<>();ArrayList<ArrayList<RecyclerView.ViewHolder>> mAdditionsList = new ArrayList<>();
ArrayList<ArrayList> mMovesList = new ArrayList<>();
ArrayList<ArrayList> mChangesList = new ArrayList<>();
能看到Google用了这么多的集合,具体作用是什么?很简单,看看runPendingAnimations()方法,我们可以得出一个结论:动画时有可能存在等待情况的!也就是说,当上一个动画还未执行完成的时候,下一个动画需要等待上一个动画执行完成后才可以执行,那万一等待了多个动画咋整,用集合保存起来呀,可是增加、删除、移动、刷新就四个动画,为啥有这么多集合,仔细看看前8个,是不是两两一组的,作用是什么呢?拿Add这组解释一下:可以看到Add这组有两个集合,mPendingAdditions和mAddAnimations,前者是等待处理的动画集合,后者就是处理中的动画集合,其他几个一样。那剩下三个是干嘛的?后面用到的时候再解释
isRunning()
@Override
public boolean isRunning() {
return (!mPendingAdditions.isEmpty() ||
!mPendingChanges.isEmpty() ||
!mPendingMoves.isEmpty() ||
!mPendingRemovals.isEmpty() ||
!mMoveAnimations.isEmpty() ||
!mRemoveAnimations.isEmpty() ||
!mAddAnimations.isEmpty() ||
!mChangeAnimations.isEmpty() ||
!mMovesList.isEmpty() ||
!mAdditionsList.isEmpty() ||
!mChangesList.isEmpty());
}
这个似乎没啥好解释的。。只要其中一个动画集合不为空,则返回true,即:还有动画正在执行或需要执行
animateAdd()
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
ViewCompat.setAlpha(holder.itemView, 0);
mPendingAdditions.add(holder);
return true;
}
一行一行的来解读,首先是resetAnimation(holder),这个方法是用来干嘛的,点进去看看:
private void resetAnimation(RecyclerView.ViewHolder holder) {
AnimatorCompatHelper.clearInterpolator(holder.itemView);
endAnimation(holder);
}
第一个方法是用来干嘛的?层层抽丝剥茧,终于找到这样一段:
@Override
public void clearInterpolator(View view) {
if (mDefaultInterpolator == null) {
mDefaultInterpolator = new ValueAnimator().getInterpolator();
}
view.animate().setInterpolator(mDefaultInterpolator);
}
}
看到了吗!view.animate().setInterpolator(),这是拿来干嘛的?
官方解释:
Sets the interpolator for the underlying animator that animates the requested properties.
翻译一下:为底层动画设置内插器,使动态化请求的属性。
具体作用是啥?就是用来修饰动画效果的,点到即止,不多做展开,来看看下一句
endAnimation(),调用结束动画
@Override
public void endAnimation(RecyclerView.ViewHolder item) {
final View view = item.itemView;
ViewCompat.animate(view).cancel();
...
for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
if (additions.remove(item)) {
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(item);
if (additions.isEmpty()) {
mAdditionsList.remove(i);
}
}
}
...
}
这一段就是将动画停止掉,将整个animateAdd方法连起来看看:
- 开始执行添加动画时,重置添加相关的动画
- 将需要执行添加动画的itemView的alpha值设置为0,即设置为不可见
将需要执行添加动画的itemView添加进等待处理添加动画的集合中
这时候就会执行runPendingAnimations()方法:runPendingAnimations()
public void runPendingAnimations() {
boolean removalsPending = !mPendingRemovals.isEmpty();//如果待执行的删除动画为空时 boolean movesPending = !mPendingMoves.isEmpty();//如果待执行的移动动画为空时 boolean changesPending = !mPendingChanges.isEmpty();//如果待执行的刷新动画为空时 boolean additionsPending = !mPendingAdditions.isEmpty();//如果待执行的添加动画为空时 if (!removalsPending && !movesPending && !additionsPending && !changesPending) { // 没有动画需要执行时 return; } // 首先,执行删除动画 for (RecyclerView.ViewHolder holder : mPendingRemovals) { animateRemoveImpl(holder); } mPendingRemovals.clear(); // 然后执行移动动画 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(); } } // 执行更新动画,与移动动画并行执行 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) { RecyclerView.ViewHolder holder = changes.get(0).oldHolder; ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration()); } else { changer.run(); } } // 执行添加动画 if (additionsPending) { final ArrayList<RecyclerView.ViewHolder> additions = new ArrayList<>(); additions.addAll(mPendingAdditions); mAdditionsList.add(additions); mPendingAdditions.clear(); Runnable adder = new Runnable() { @Override public void run() { //将所有添加动画放在一块执行 for (RecyclerView.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(); } } }
以上就是处理待执行动画的所有步骤,有详细注释就不做解释了,细心的同学可能有注意到,添加、删除、移动、更新动画都有一个impl方法,这个方法里就是具体的动画!先来看看添加动画:
animateAddImpl
void animateAddImpl(final RecyclerView.ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
mAddAnimations.add(holder);
animation.alpha(1).setDuration(getAddDuration()).
setListener(new VpaListenerAdapter() {
//动画开始时,调用dispatchAddStarting,即通知可以开始动画了!
@Override
public void onAnimationStart(View view) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationCancel(View view) {
ViewCompat.setAlpha(view, 1);
}
//动画结束时,清空动画的监听,通知动画已结束,将处理中的动画集合移除当前这一个,如果当前已经没有动画在执行了,结束所有动画
@Override
public void onAnimationEnd(View view) {
animation.setListener(null);
dispatchAddFinished(holder);
mAddAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
简单讲讲animateMove:
animateMove()
@Override
public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY,
int toX, int toY) {
final View view = holder.itemView;
fromX += ViewCompat.getTranslationX(holder.itemView);
fromY += ViewCompat.getTranslationY(holder.itemView);
resetAnimation(holder);
int deltaX = toX - fromX;
int deltaY = toY - fromY;
if (deltaX == 0 && deltaY == 0) {
dispatchMoveFinished(holder);
return false;
}
if (deltaX != 0) {
ViewCompat.setTranslationX(view, -deltaX);
}
if (deltaY != 0) {
ViewCompat.setTranslationY(view, -deltaY);
}
mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
return true;
}
其实很好理解,就说一下deltaX和deltaY,Google这样计算的目的就是为了在上一个item在移出的时候,当前这个item不会立即移动到当前上一个item的位置。
其他几个动画类似,就不做解释了,在这里也可以看出,具体是用的什么动画,动画时什么效果,都是在animatexxximpl中执行的,如果想要定制自己的动画,直接改这几个方法就可以了
接下来讲讲如何定制属于自己的item动画
第二部分
前面说了,动画的具体实现都是在各个animateXXXImpl()中,那么要定制属于我们自己item动画,只需要更改animateXXXImpl()方法就可以了,那么我们先来做一个自己的添加动画
animateRemoveImpl()
期望效果:这个item删除时,逐渐透明且慢慢缩小
很简单的效果,我们只需要设置alpha,scaleX和scaleY就可以了:
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
mRemoveAnimations.add(holder);
animation.setDuration(getRemoveDuration())
.alpha(0).scaleY(0).scaleX(0).setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(View view) {
animation.setListener(null);
ViewCompat.setAlpha(view, 1);
ViewCompat.setScaleY(view, 1);
ViewCompat.setScaleX(view, 1);
dispatchRemoveFinished(holder);
mRemoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
animateAddImpl()
期望效果:从透明变为可见,并从小拉伸到正常大小
其实就是跟remove反过来了而已,来看看代码:
void animateAddImpl(final RecyclerView.ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
mAddAnimations.add(holder);
animation.alpha(1).scaleX(1).scaleY(1).setDuration(getAddDuration()).
setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationCancel(View view) {
ViewCompat.setScaleX(view, 1);
ViewCompat.setScaleY(view, 1);
ViewCompat.setAlpha(view, 1);
}
@Override
public void onAnimationEnd(View view) {
animation.setListener(null);
dispatchAddFinished(holder);
mAddAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
来看看效果:
添加Item的动画跟想象的不一样啊!回头看看animateAdd():
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
ViewCompat.setAlpha(holder.itemView, 0);
mPendingAdditions.add(holder);
return true;
}
这里还开始动画时只设置了alpha为透明,并没有把item缩小,那么改一下:
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
ViewCompat.setAlpha(holder.itemView, 0);
ViewCompat.setScaleX(holder.itemView, 0);
ViewCompat.setScaleY(holder.itemView, 0);
mPendingAdditions.add(holder);
return true;
}
再来看看效果:
这下对了,快速连续点几下试试看呢:
为啥前几个item都是透明的啊!刷新看看,又正常显示了!咋回事,想想最开始说的animateAdd(),我们在添加动画之前,先重置了动画,也就是resetAnimation(),点进去找到endAnimation(),看看添加相关的代码块:
for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
if (additions.remove(item)) {
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(item);
if (additions.isEmpty()) {
mAdditionsList.remove(i);
}
}
这下原因明了了,在我们快速点击添加时,会先执行这里面的代码,而这里只设置了alpha值,修改一下:
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.setScaleY(view,1);
ViewCompat.setScaleX(view,1);
dispatchAddFinished(item);
if (additions.isEmpty()) {
mAdditionsList.remove(i);
}
}
再来看看效果:
完美
结语
只讲了添加删除的动画,move和change都没有改动,篇幅太长就不做演示了,有兴趣可以自己尝试一下,不过难度最大的还是move,change的动画,毕竟要计算坐标,相对添加删除来说要难一点点,虽然也差不了多少,本篇到此结束,文中如有错误的地方,还请指出,谢谢!