由动画Animator.end()引发的问题总结

最近在工作过程遇到过一个关于调用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!

    推荐阅读