这一章节主要来了解SimpleItemAnimator和DefaultItemAnimator这两个类
先来看一下SimpleItemAnimator,直接上图
上图是SDK中SimpleItemAnimator的继承关系,可以看到它直接继承自ItemAnimator类,注释信息主要的意思就是它是对ItemAnimator的一个封装类,通过记录View的边界信息来决定需要执行的move、change、add还是remove等动画效果。并且这个类也复用了ItemAnimator中的一些方法,比如它还是使用ItemHolderInfo来保存某一View的信息,具体不了解的可以查看我的上一篇博客 RecyclerView.ItemAnimator终极解读(一)--RecyclerView源码解析 。你可以复写obtainHolderInfo()方法来返回你自定义的ItemHolderInfo信息。
在上一篇博客的最后,我们已经知道,最终就是调用ItemAnimator的animateChange、animateAppearance、runPendingAnimations等方法来显示和执行动画效果,那SimpleItemAnimator对这几个方法是如何封装的呢?
还是以添加一个Item举例,最后调用的是animateAppearance方法,SimpleItemAnimator的animateAppearance方法代码如下:
@Override
public boolean animateAppearance(@NonNull ViewHolder viewHolder,
@Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left
|| preLayoutInfo.top != postLayoutInfo.top)) {
// slide items in if before/after locations differ
if (DEBUG) {
Log.d(TAG, "APPEARING: " + viewHolder + " with view " + viewHolder);
}
return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top,
postLayoutInfo.left, postLayoutInfo.top);
} else {
if (DEBUG) {
Log.d(TAG, "ADDED: " + viewHolder + " with view " + viewHolder);
}
return animateAdd(viewHolder);
}
}
其实封装的很简单,只是判断了一下布局前后两个ViewHolder的left/top坐标是否相等 如果不相等则调用animateMove,否则调用animateAdd(viewHolder)方法。
整理一下其实就是给animateAppearance改了一下名字而已,改成animateAdd方法更加见名知意罢了
animateAdd(viewHolder)是一个抽象方法,需要子类去实现。
注意:SimpleItemAnimator并没有实现runPendingAnimations方法,因此如果继承自SimpleItemAnimator来实现自定义动画的话,还是需要自己书写执行动画的代码
接下来看一下DefaultItemAnimator,它是Android SDK中提供的一个默认动画实现类,如果产品并没有复杂的动画需求,可以直接使用这个API,实现简单的动画效果
可以看到DefaultItemAnimator继承自SimpleItemAnimator,因此如果想实现添加一个Item的动画效果,只需要实现两个方法,在animateAdd方法中将ViewHolder添加到集合中,
然后在runPendingAnimations方法中执行所有的add动画即可, 如以下代码所示:
animateAdd
@Override
public boolean animateAdd(final ViewHolder holder) {
resetAnimation(holder);
ViewCompat.setAlpha(holder.itemView, 0);
mPendingAdditions.add(holder);
return true;
}
将holder添加到mPendingAdditions集合中,注意:此集合一会儿会在runPendingAnimations方法中进行遍历执行动画
@Override
public void runPendingAnimations() {
boolean removalsPending = !mPendingRemovals.isEmpty();
boolean movesPending = !mPendingMoves.isEmpty();
boolean changesPending = !mPendingChanges.isEmpty();
boolean additionsPending = !mPendingAdditions.isEmpty();
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
// nothing to animate
return;
}
// First, remove stuff
for (ViewHolder holder : mPendingRemovals) {
animateRemoveImpl(holder);
}
mPendingRemovals.clear();
// Next, move stuff
if (movesPending) {
final ArrayList 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
if (changesPending) {
final ArrayList 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 additions = new ArrayList<>();
additions.addAll(mPendingAdditions);
mAdditionsList.add(additions);
mPendingAdditions.clear();
Runnable adder = new Runnable() {
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();
}
}
}
12行~14行 remove最先执行,for循环遍历mPendingRemovals集合中所有的ViewHolder,然后调用animateRemoveImpl(holder)执行删除动画,代码如下所示:
private void animateRemoveImpl(final ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
mRemoveAnimations.add(holder);
animation.setDuration(getRemoveDuration())
.alpha(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);
dispatchRemoveFinished(holder);
mRemoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
17行~62行:remove执行完之后,再同事开始move和change动画,
move和change执行完之后,最后执行add动画,调用的是animateAddImpl(holder)方法,代码如下所示:
private void animateAddImpl(final ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
mAddAnimations.add(holder);
animation.alpha(1).setDuration(getAddDuration()).
setListener(new VpaListenerAdapter() {
@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();
}
可以看出默认的DefaultItemAnimator添加item动画实际上就是一个透明度的变化效果。
注意:在DefaultItemAnimator中的mPendingRemovals和mPendingAdditions都是私有的,因此如果要继承DefaultItemAnimator实现自定义动画的话,无法通过在animateAdd方法中向此集合添加ViewHolder来实现。只能复写animateAdd、animateChange等方法,在这些方法中主动调用Animation.start方法执行动画类
后续两节会依次介绍通过继承SimpleItemAnimator和DefaultItemAnimator来实现自定义Item动画