diff options
4 files changed, 73 insertions, 22 deletions
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 4a07de0410ae..36308e5e75b0 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -423,15 +423,19 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim notifyListeners(AnimatorCaller.ON_CANCEL, false); callOnPlayingSet(Animator::cancel); mPlayingSet.clear(); - // If the end callback is pending, invoke the end callbacks of the animator nodes before - // ending this set. Pass notifyListeners=false because this endAnimation will do that. - if (consumePendingEndListeners(false /* notifyListeners */)) { - for (int i = mNodeMap.size() - 1; i >= 0; i--) { - mNodeMap.keyAt(i).consumePendingEndListeners(true /* notifyListeners */); - } + endAnimationAndNotifyEndListenersImmediately(); + } + } + + private void endAnimationAndNotifyEndListenersImmediately() { + // If the end callback is pending, invoke the end callbacks of the animator nodes before + // ending this set. Pass notifyListeners=false because endAnimation will do that. + if (consumePendingEndListeners(false /* notifyListeners */)) { + for (int i = mNodeMap.size() - 1; i >= 0; i--) { + mNodeMap.keyAt(i).consumePendingEndListeners(true /* notifyListeners */); } - endAnimation(); } + endAnimation(); } /** @@ -529,7 +533,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } } } - endAnimation(); + endAnimationAndNotifyEndListenersImmediately(); } /** @@ -1455,8 +1459,6 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim private void endAnimation(boolean fromLastFrame) { final boolean postNotifyEndListener = sPostNotifyEndListenerEnabled && mListeners != null && fromLastFrame && mTotalDuration > 0; - mStarted = false; - mLastFrameTime = -1; mFirstFrame = -1; mLastEventId = -1; mPaused = false; @@ -1466,11 +1468,18 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim // No longer receive callbacks removeAnimationCallback(); + // If postNotifyEndListener is false (most cases), then it is the same as calling + // completeEndAnimation directly. notifyEndListenersFromEndAnimation(mReversing, postNotifyEndListener); } @Override void completeEndAnimation(boolean isReversing, String notifyListenerTraceName) { + // The mStarted and mLastFrameTime are reset here because isStarted() and isRunning() + // can be true before notifying the end listeners. When notifying the end listeners, + // isStarted() and isRunning() should be false. + mStarted = false; + mLastFrameTime = -1; super.completeEndAnimation(isReversing, notifyListenerTraceName); removeAnimationEndListener(); mSelfPulse = true; diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index fbcc73ea59e7..8d34090c8f60 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -1212,6 +1212,10 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio initAnimation(); } animateValue(shouldPlayBackward(mRepeatCount, mReversing) ? 0f : 1f); + if (mAnimationEndRequested) { + consumePendingEndListeners(true /* notifyListeners */); + return; + } endAnimation(); } @@ -1308,8 +1312,8 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio mLastFrameTime = -1; mFirstFrameTime = -1; mStartTime = -1; - mRunning = false; - mStarted = false; + // If postNotifyEndListener is false (most cases), then it is the same as calling + // completeEndAnimation directly. notifyEndListenersFromEndAnimation(mReversing, postNotifyEndListener); if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(), @@ -1319,6 +1323,11 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio @Override void completeEndAnimation(boolean isReversing, String notifyListenerTraceName) { + // The mRunning and mStarted are reset here because isStarted() and isRunning() + // can be true before notifying the end listeners. When notifying the end listeners, + // isStarted() and isRunning() should be false. + mRunning = false; + mStarted = false; super.completeEndAnimation(isReversing, notifyListenerTraceName); // mReversing needs to be reset *after* notifying the listeners for the end callbacks. mReversing = false; diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java index 6d86bd209a3d..746ba9659c83 100644 --- a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java +++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java @@ -38,6 +38,7 @@ import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; @MediumTest @@ -490,9 +491,24 @@ public class AnimatorSetCallsTest { @Test public void testCancelOnPendingEndListener() throws Throwable { + testPendingEndListener(AnimatorSet::cancel); + } + + @Test + public void testEndOnPendingEndListener() throws Throwable { + testPendingEndListener(animatorSet -> { + // This verifies that isRunning() and isStarted() are true at last frame. + // Then the end() should invoke the end callback immediately. + if (animatorSet.isRunning() && animatorSet.isStarted()) { + animatorSet.end(); + } + }); + } + + private void testPendingEndListener(Consumer<AnimatorSet> finishOnLastFrame) throws Throwable { final CountDownLatch endLatch = new CountDownLatch(1); final Handler handler = new Handler(Looper.getMainLooper()); - final boolean[] endCalledRightAfterCancel = new boolean[2]; + final boolean[] endCalledImmediately = new boolean[2]; final AnimatorSet set = new AnimatorSet(); final ValueAnimatorTests.MyListener asListener = new ValueAnimatorTests.MyListener(); final ValueAnimatorTests.MyListener vaListener = new ValueAnimatorTests.MyListener(); @@ -502,9 +518,9 @@ public class AnimatorSetCallsTest { va.addUpdateListener(animation -> { if (animation.getAnimatedFraction() == 1f) { handler.post(() -> { - set.cancel(); - endCalledRightAfterCancel[0] = vaListener.endCalled; - endCalledRightAfterCancel[1] = asListener.endCalled; + finishOnLastFrame.accept(set); + endCalledImmediately[0] = vaListener.endCalled; + endCalledImmediately[1] = asListener.endCalled; endLatch.countDown(); }); } @@ -517,8 +533,8 @@ public class AnimatorSetCallsTest { try { handler.post(set::start); assertTrue(endLatch.await(1, TimeUnit.SECONDS)); - assertTrue(endCalledRightAfterCancel[0]); - assertTrue(endCalledRightAfterCancel[1]); + assertTrue(endCalledImmediately[0]); + assertTrue(endCalledImmediately[1]); } finally { ValueAnimator.setPostNotifyEndListenerEnabled(false); } diff --git a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java index a55909f0c193..89664fff979b 100644 --- a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java +++ b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java @@ -44,6 +44,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) @MediumTest @@ -923,9 +924,25 @@ public class ValueAnimatorTests { @Test public void testCancelOnPendingEndListener() throws Throwable { + testPendingEndListener(ValueAnimator::cancel); + } + + @Test + public void testEndOnPendingEndListener() throws Throwable { + testPendingEndListener(animator -> { + // This verifies that isRunning() and isStarted() are true at last frame. + // Then the end() should invoke the end callback immediately. + if (animator.isRunning() && animator.isStarted()) { + animator.end(); + } + }); + } + + private void testPendingEndListener(Consumer<ValueAnimator> finishOnLastFrame) + throws Throwable { + final boolean[] endCalledImmediately = new boolean[1]; final CountDownLatch endLatch = new CountDownLatch(1); final Handler handler = new Handler(Looper.getMainLooper()); - final boolean[] endCalledRightAfterCancel = new boolean[1]; final MyListener listener = new MyListener(); final ValueAnimator va = new ValueAnimator(); va.setFloatValues(0f, 1f); @@ -933,8 +950,8 @@ public class ValueAnimatorTests { va.addUpdateListener(animation -> { if (animation.getAnimatedFraction() == 1f) { handler.post(() -> { - va.cancel(); - endCalledRightAfterCancel[0] = listener.endCalled; + finishOnLastFrame.accept(va); + endCalledImmediately[0] = listener.endCalled; endLatch.countDown(); }); } @@ -945,7 +962,7 @@ public class ValueAnimatorTests { try { handler.post(va::start); assertThat(endLatch.await(1, TimeUnit.SECONDS)).isTrue(); - assertThat(endCalledRightAfterCancel[0]).isTrue(); + assertThat(endCalledImmediately[0]).isTrue(); } finally { ValueAnimator.setPostNotifyEndListenerEnabled(false); } |