summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/animation/AnimatorSet.java29
-rw-r--r--core/java/android/animation/ValueAnimator.java13
-rw-r--r--core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java28
-rw-r--r--core/tests/coretests/src/android/animation/ValueAnimatorTests.java25
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);
}