最近在工作过程遇到过一个关于调用Animator.end()方法引发的Bug,下面让我来说说是怎么回事。
在开发过程中,由于一些业务或者需求功能的需要,我们经常需要在某些环节中同时或者连续播放多个动画,这是我们一般会new 一个AnimatorSet来做此类工作,比如常见的方式如下所示:
AnimatorSet mSet = new AnimatorSet();
ValueAnimator animatorA = ValueAnimator.ofFloat(0,200f);
animatorA.setDuration(1000);
animatorA.removeAllListeners();
animatorA.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
Log.i("wu", "animatorA onAnimationStart");
}@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
Log.i("wu", "animatorA onAnimationEnd");
}
});
ValueAnimator animatorB = ValueAnimator.ofFloat(0,200f);
animatorB.setDuration(3000);
animatorB.removeAllListeners();
animatorB.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
Log.i("wu", "animatorB onAnimationStart");
}@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
Log.i("wu", "animatorB onAnimationEnd");
}
});
mSet.playTogether(animatorA, animatorB);
mSet.start()
从代码可以看出,animatorA执行时间只有1000ms,animatorB执行时间有3000ms,AnimationSet一般没有主动调用setDuration()设置时间的话,AnimationSet的执行时间会按照里面的动画全部执行完为止,但是如果有调用setDuration(),则这个时间也会重新设置给里面的每个子动画,也就是说之前子动画设置的时间就没效果了,当然如果调用AnimationSet的setInterpolator()也会影响子动画。 假如在mSet.start()执行后,如果有需要,可能会在中途调用mSet.end()方法来终止动画,但是由于animatorA和animatorB的执行时间不同,这里就要区分mSet.end()的调用时机,假如animatorA还没执行完就调用了end,打印的log如下:
10-17 16:14:07.615 5406-5406/com.example.mydemo I/wu: animatorA onAnimationEnd
10-17 16:14:07.615 5406-5406/com.example.mydemo I/wu: animatorB onAnimationEndonAnimationEnd
假如是在animatorA执行完后,animatorB没有执行完前,即在start开始后,过了1000,而没有超过3000ms期间调用end方法,打印的log如下:
10-17 16:14:07.615 5406-5406/com.example.mydemo I/wu: animatorA onAnimationStart
10-17 16:14:07.615 5406-5406/com.example.mydemo I/wu: animatorA onAnimationEnd
10-17 16:14:07.615 5406-5406/com.example.mydemo I/wu: animatorB onAnimationEnd
对比两次的log结果,可以看出animatorA的onAnimationStart回调居然被调用了一次,这就是问题所在,也是很让人无法理解的,既然animatorA都执行完了,结果还先调用了一次onAnimationStart,最后在没有办法的情况下,只有去看Android原生代码寻找答案,通过跟踪源码发现在VauleAnimatior的end方法中有如下一段:
protected void endAnimation(AnimationHandler handler) {
handler.mAnimations.remove(this);
handler.mPendingAnimations.remove(this);
handler.mDelayedAnims.remove(this);
mPlayingState = STOPPED;
mPaused = false;
if ((mStarted || mRunning) && mListeners != null) {
if (!mRunning) {
// If it's not yet running, then start listeners weren't called. Call them now.
notifyStartListeners();
}
ArrayList tmpListeners =
(ArrayList) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0;
i < numListeners;
++i) {
tmpListeners.get(i).onAnimationEnd(this);
}
}
【由动画Animator.end()引发的问题总结】看到这段源码,顿时明白了,原来在end的时候如果发现当前动画没有正在执行, 就会先通过调用了一次onAnimationStart,之后下面再调用onAnimationEnd,以此来保证完整的调用。 最后,得出这样子的结论:在animator 已经start并且结束后,如果再调用了其end方法,会导致onAnimationStart和onAnimationEnd调用,但是如果调用的是cancel而不是end方法,就不会触发任何回调。所以在开发过程中,如果想要调用end方法,最好先调用isRunning() 判断下当前动画是否还在执行,只有还在执行时才去调用end方法.不然可能会出现比较诡异的动画异常bug!