Loading core/java/android/animation/Animator.java +99 −16 Original line number Diff line number Diff line Loading @@ -71,6 +71,13 @@ public abstract class Animator implements Cloneable { */ private static long sBackgroundPauseDelay = 10000; /** * A cache of the values in a list. Used so that when calling the list, we have a copy * of it in case the list is modified while iterating. The array can be reused to avoid * allocation on every notification. */ private Object[] mCachedList; /** * Sets the duration for delaying pausing animators when apps go into the background. * Used by AnimationHandler when requested to pause animators. Loading Loading @@ -160,14 +167,7 @@ public abstract class Animator implements Cloneable { public void pause() { if (isStarted() && !mPaused) { mPaused = true; if (mPauseListeners != null) { ArrayList<AnimatorPauseListener> tmpListeners = (ArrayList<AnimatorPauseListener>) mPauseListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationPause(this); } } notifyPauseListeners(AnimatorCaller.ON_PAUSE); } } Loading @@ -184,14 +184,7 @@ public abstract class Animator implements Cloneable { public void resume() { if (mPaused) { mPaused = false; if (mPauseListeners != null) { ArrayList<AnimatorPauseListener> tmpListeners = (ArrayList<AnimatorPauseListener>) mPauseListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationResume(this); } } notifyPauseListeners(AnimatorCaller.ON_RESUME); } } Loading Loading @@ -450,6 +443,7 @@ public abstract class Animator implements Cloneable { if (mPauseListeners != null) { anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners); } anim.mCachedList = null; return anim; } catch (CloneNotSupportedException e) { throw new AssertionError(); Loading Loading @@ -590,6 +584,70 @@ public abstract class Animator implements Cloneable { } } /** * Calls notification for each AnimatorListener. * * @param notification The notification method to call on each listener. * @param isReverse When this is used with start/end, this is the isReverse parameter. For * other calls, this is ignored. */ void notifyListeners( AnimatorCaller<AnimatorListener, Animator> notification, boolean isReverse ) { callOnList(mListeners, notification, this, isReverse); } /** * Call pause/resume on each AnimatorPauseListener. * * @param notification Either ON_PAUSE or ON_RESUME to call onPause or onResume on each * listener. */ void notifyPauseListeners(AnimatorCaller<AnimatorPauseListener, Animator> notification) { callOnList(mPauseListeners, notification, this, false); } /** * Calls <code>call</code> for every item in <code>list</code> with <code>animator</code> and * <code>isReverse</code> as parameters. * * @param list The list of items to make calls on. * @param call The method to call for each item in list. * @param animator The animator parameter of call. * @param isReverse The isReverse parameter of call. * @param <T> The item type of list * @param <A> The Animator type of animator. */ <T, A> void callOnList( ArrayList<T> list, AnimatorCaller<T, A> call, A animator, boolean isReverse ) { int size = list == null ? 0 : list.size(); if (size > 0) { // Try to reuse mCacheList to store the items of list. Object[] array; if (mCachedList == null || mCachedList.length < size) { array = new Object[size]; } else { array = mCachedList; // Clear it in case there is some reentrancy mCachedList = null; } list.toArray(array); for (int i = 0; i < size; i++) { //noinspection unchecked T item = (T) array[i]; call.call(item, animator, isReverse); array[i] = null; } // Store it for the next call so we can reuse this array, if needed. mCachedList = array; } } /** * <p>An animation listener receives notifications from an animation. * Notifications indicate animation related events, such as the end or the Loading Loading @@ -748,4 +806,29 @@ public abstract class Animator implements Cloneable { return clone; } } /** * Internally used by {@link #callOnList(ArrayList, AnimatorCaller, Object, boolean)} to * make a call on all children of a list. This can be for start, stop, pause, cancel, update, * etc notifications. * * @param <T> The type of listener to make the call on * @param <A> The type of animator that is passed as a parameter */ interface AnimatorCaller<T, A> { void call(T listener, A animator, boolean isReverse); AnimatorCaller<AnimatorListener, Animator> ON_START = AnimatorListener::onAnimationStart; AnimatorCaller<AnimatorListener, Animator> ON_END = AnimatorListener::onAnimationEnd; AnimatorCaller<AnimatorListener, Animator> ON_CANCEL = (listener, animator, isReverse) -> listener.onAnimationCancel(animator); AnimatorCaller<AnimatorListener, Animator> ON_REPEAT = (listener, animator, isReverse) -> listener.onAnimationRepeat(animator); AnimatorCaller<AnimatorPauseListener, Animator> ON_PAUSE = (listener, animator, isReverse) -> listener.onAnimationPause(animator); AnimatorCaller<AnimatorPauseListener, Animator> ON_RESUME = (listener, animator, isReverse) -> listener.onAnimationResume(animator); AnimatorCaller<ValueAnimator.AnimatorUpdateListener, ValueAnimator> ON_UPDATE = (listener, animator, isReverse) -> listener.onAnimationUpdate(animator); } } core/java/android/animation/AnimatorSet.java +22 −27 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.function.Consumer; /** * This class plays a set of {@link Animator} objects in the specified order. Animations Loading Loading @@ -424,24 +425,28 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim throw new AndroidRuntimeException("Animators may only be run on Looper threads"); } if (isStarted()) { ArrayList<AnimatorListener> tmpListeners = null; if (mListeners != null) { tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); int size = tmpListeners.size(); for (int i = 0; i < size; i++) { tmpListeners.get(i).onAnimationCancel(this); } } ArrayList<Node> playingSet = new ArrayList<>(mPlayingSet); int setSize = playingSet.size(); for (int i = 0; i < setSize; i++) { playingSet.get(i).mAnimation.cancel(); } notifyListeners(AnimatorCaller.ON_CANCEL, false); callOnPlayingSet(Animator::cancel); mPlayingSet.clear(); endAnimation(); } } /** * Calls consumer on every Animator of mPlayingSet. * * @param consumer The method to call on every Animator of mPlayingSet. */ private void callOnPlayingSet(Consumer<Animator> consumer) { final ArrayList<Node> list = mPlayingSet; final int size = list.size(); //noinspection ForLoopReplaceableByForEach for (int i = 0; i < size; i++) { final Animator animator = list.get(i).mAnimation; consumer.accept(animator); } } // Force all the animations to end when the duration scale is 0. private void forceToEnd() { if (mEndCanBeCalled) { Loading Loading @@ -662,6 +667,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim super.pause(); if (!previouslyPaused && mPaused) { mPauseTime = -1; callOnPlayingSet(Animator::pause); } } Loading @@ -676,6 +682,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim if (mPauseTime >= 0) { addAnimationCallback(0); } callOnPlayingSet(Animator::resume); } } Loading Loading @@ -751,26 +758,14 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim private void notifyStartListeners(boolean inReverse) { if (mListeners != null && !mStartListenersCalled) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { AnimatorListener listener = tmpListeners.get(i); listener.onAnimationStart(this, inReverse); } notifyListeners(AnimatorCaller.ON_START, inReverse); } mStartListenersCalled = true; } private void notifyEndListeners(boolean inReverse) { if (mListeners != null && mStartListenersCalled) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { AnimatorListener listener = tmpListeners.get(i); listener.onAnimationEnd(this, inReverse); } notifyListeners(AnimatorCaller.ON_END, inReverse); } mStartListenersCalled = false; } Loading core/java/android/animation/ValueAnimator.java +6 −39 Original line number Diff line number Diff line Loading @@ -1110,24 +1110,14 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio private void notifyStartListeners(boolean isReversing) { if (mListeners != null && !mStartListenersCalled) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationStart(this, isReversing); } notifyListeners(AnimatorCaller.ON_START, isReversing); } mStartListenersCalled = true; } private void notifyEndListeners(boolean isReversing) { if (mListeners != null && mStartListenersCalled) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationEnd(this, isReversing); } notifyListeners(AnimatorCaller.ON_END, isReversing); } mStartListenersCalled = false; } Loading Loading @@ -1224,15 +1214,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio // If it's not yet running, then start listeners weren't called. Call them now. notifyStartListeners(mReversing); } int listenersSize = mListeners.size(); if (listenersSize > 0) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); for (int i = 0; i < listenersSize; i++) { AnimatorListener listener = tmpListeners.get(i); listener.onAnimationCancel(this); } } notifyListeners(AnimatorCaller.ON_CANCEL, false); } endAnimation(); Loading Loading @@ -1435,12 +1417,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio done = true; } else if (newIteration && !lastIterationFinished) { // Time to repeat if (mListeners != null) { int numListeners = mListeners.size(); for (int i = 0; i < numListeners; ++i) { mListeners.get(i).onAnimationRepeat(this); } } notifyListeners(AnimatorCaller.ON_REPEAT, false); } else if (lastIterationFinished) { done = true; } Loading Loading @@ -1494,12 +1471,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio lastIteration = Math.min(lastIteration, mRepeatCount); if (iteration != lastIteration) { if (mListeners != null) { int numListeners = mListeners.size(); for (int i = 0; i < numListeners; ++i) { mListeners.get(i).onAnimationRepeat(this); } } notifyListeners(AnimatorCaller.ON_REPEAT, false); } } Loading Loading @@ -1697,12 +1669,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio for (int i = 0; i < numValues; ++i) { mValues[i].calculateValue(fraction); } if (mUpdateListeners != null) { int numListeners = mUpdateListeners.size(); for (int i = 0; i < numListeners; ++i) { mUpdateListeners.get(i).onAnimationUpdate(this); } } callOnList(mUpdateListeners, AnimatorCaller.ON_UPDATE, this, false); } @Override Loading core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java 0 → 100644 +251 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.animation; import static org.junit.Assert.assertEquals; import android.util.PollingCheck; import android.view.View; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.filters.MediumTest; import com.android.frameworks.coretests.R; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @MediumTest public class AnimatorSetCallsTest { @Rule public final ActivityScenarioRule<AnimatorSetActivity> mRule = new ActivityScenarioRule<>(AnimatorSetActivity.class); private AnimatorSetActivity mActivity; private AnimatorSet mSet1; private CountListener mListener1; private CountListener mListener2; private CountListener mListener3; @Before public void setUp() throws Exception { mRule.getScenario().onActivity((activity) -> { mActivity = activity; View square = mActivity.findViewById(R.id.square1); mSet1 = new AnimatorSet(); mListener1 = new CountListener(); mSet1.addListener(mListener1); mSet1.addPauseListener(mListener1); AnimatorSet set2 = new AnimatorSet(); mListener2 = new CountListener(); set2.addListener(mListener2); set2.addPauseListener(mListener2); ObjectAnimator anim = ObjectAnimator.ofFloat(square, "translationX", 0f, 100f); mListener3 = new CountListener(); anim.addListener(mListener3); anim.addPauseListener(mListener3); anim.setDuration(1); set2.play(anim); mSet1.play(set2); }); } @Test public void startEndCalledOnChildren() { mRule.getScenario().onActivity((a) -> mSet1.start()); waitForOnUiThread(() -> mListener1.endForward > 0); // only startForward and endForward should have been called once mListener1.assertValues( 1, 0, 1, 0, 0, 0, 0, 0 ); mListener2.assertValues( 1, 0, 1, 0, 0, 0, 0, 0 ); mListener3.assertValues( 1, 0, 1, 0, 0, 0, 0, 0 ); } @Test public void cancelCalledOnChildren() { mRule.getScenario().onActivity((a) -> { mSet1.start(); mSet1.cancel(); }); waitForOnUiThread(() -> mListener1.endForward > 0); // only startForward and endForward should have been called once mListener1.assertValues( 1, 0, 1, 0, 1, 0, 0, 0 ); mListener2.assertValues( 1, 0, 1, 0, 1, 0, 0, 0 ); mListener3.assertValues( 1, 0, 1, 0, 1, 0, 0, 0 ); } @Test public void startEndReversedCalledOnChildren() { mRule.getScenario().onActivity((a) -> mSet1.reverse()); waitForOnUiThread(() -> mListener1.endReverse > 0); // only startForward and endForward should have been called once mListener1.assertValues( 0, 1, 0, 1, 0, 0, 0, 0 ); mListener2.assertValues( 0, 1, 0, 1, 0, 0, 0, 0 ); mListener3.assertValues( 0, 1, 0, 1, 0, 0, 0, 0 ); } @Test public void pauseResumeCalledOnChildren() { mRule.getScenario().onActivity((a) -> { mSet1.start(); mSet1.pause(); }); waitForOnUiThread(() -> mListener1.pause > 0); // only startForward and pause should have been called once mListener1.assertValues( 1, 0, 0, 0, 0, 0, 1, 0 ); mListener2.assertValues( 1, 0, 0, 0, 0, 0, 1, 0 ); mListener3.assertValues( 1, 0, 0, 0, 0, 0, 1, 0 ); mRule.getScenario().onActivity((a) -> mSet1.resume()); waitForOnUiThread(() -> mListener1.endForward > 0); // resume and endForward should have been called once mListener1.assertValues( 1, 0, 1, 0, 0, 0, 1, 1 ); mListener2.assertValues( 1, 0, 1, 0, 0, 0, 1, 1 ); mListener3.assertValues( 1, 0, 1, 0, 0, 0, 1, 1 ); } private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) { final boolean[] value = new boolean[1]; PollingCheck.waitFor(() -> { mActivity.runOnUiThread(() -> value[0] = condition.canProceed()); return value[0]; }); } private static class CountListener implements Animator.AnimatorListener, Animator.AnimatorPauseListener { public int startNoParam; public int endNoParam; public int startReverse; public int startForward; public int endForward; public int endReverse; public int cancel; public int repeat; public int pause; public int resume; public void assertValues( int startForward, int startReverse, int endForward, int endReverse, int cancel, int repeat, int pause, int resume ) { assertEquals(0, startNoParam); assertEquals(0, endNoParam); assertEquals(startForward, this.startForward); assertEquals(startReverse, this.startReverse); assertEquals(endForward, this.endForward); assertEquals(endReverse, this.endReverse); assertEquals(cancel, this.cancel); assertEquals(repeat, this.repeat); assertEquals(pause, this.pause); assertEquals(resume, this.resume); } @Override public void onAnimationStart(Animator animation, boolean isReverse) { if (isReverse) { startReverse++; } else { startForward++; } } @Override public void onAnimationEnd(Animator animation, boolean isReverse) { if (isReverse) { endReverse++; } else { endForward++; } } @Override public void onAnimationStart(Animator animation) { startNoParam++; } @Override public void onAnimationEnd(Animator animation) { endNoParam++; } @Override public void onAnimationCancel(Animator animation) { cancel++; } @Override public void onAnimationRepeat(Animator animation) { repeat++; } @Override public void onAnimationPause(Animator animation) { pause++; } @Override public void onAnimationResume(Animator animation) { resume++; } } } Loading
core/java/android/animation/Animator.java +99 −16 Original line number Diff line number Diff line Loading @@ -71,6 +71,13 @@ public abstract class Animator implements Cloneable { */ private static long sBackgroundPauseDelay = 10000; /** * A cache of the values in a list. Used so that when calling the list, we have a copy * of it in case the list is modified while iterating. The array can be reused to avoid * allocation on every notification. */ private Object[] mCachedList; /** * Sets the duration for delaying pausing animators when apps go into the background. * Used by AnimationHandler when requested to pause animators. Loading Loading @@ -160,14 +167,7 @@ public abstract class Animator implements Cloneable { public void pause() { if (isStarted() && !mPaused) { mPaused = true; if (mPauseListeners != null) { ArrayList<AnimatorPauseListener> tmpListeners = (ArrayList<AnimatorPauseListener>) mPauseListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationPause(this); } } notifyPauseListeners(AnimatorCaller.ON_PAUSE); } } Loading @@ -184,14 +184,7 @@ public abstract class Animator implements Cloneable { public void resume() { if (mPaused) { mPaused = false; if (mPauseListeners != null) { ArrayList<AnimatorPauseListener> tmpListeners = (ArrayList<AnimatorPauseListener>) mPauseListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationResume(this); } } notifyPauseListeners(AnimatorCaller.ON_RESUME); } } Loading Loading @@ -450,6 +443,7 @@ public abstract class Animator implements Cloneable { if (mPauseListeners != null) { anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners); } anim.mCachedList = null; return anim; } catch (CloneNotSupportedException e) { throw new AssertionError(); Loading Loading @@ -590,6 +584,70 @@ public abstract class Animator implements Cloneable { } } /** * Calls notification for each AnimatorListener. * * @param notification The notification method to call on each listener. * @param isReverse When this is used with start/end, this is the isReverse parameter. For * other calls, this is ignored. */ void notifyListeners( AnimatorCaller<AnimatorListener, Animator> notification, boolean isReverse ) { callOnList(mListeners, notification, this, isReverse); } /** * Call pause/resume on each AnimatorPauseListener. * * @param notification Either ON_PAUSE or ON_RESUME to call onPause or onResume on each * listener. */ void notifyPauseListeners(AnimatorCaller<AnimatorPauseListener, Animator> notification) { callOnList(mPauseListeners, notification, this, false); } /** * Calls <code>call</code> for every item in <code>list</code> with <code>animator</code> and * <code>isReverse</code> as parameters. * * @param list The list of items to make calls on. * @param call The method to call for each item in list. * @param animator The animator parameter of call. * @param isReverse The isReverse parameter of call. * @param <T> The item type of list * @param <A> The Animator type of animator. */ <T, A> void callOnList( ArrayList<T> list, AnimatorCaller<T, A> call, A animator, boolean isReverse ) { int size = list == null ? 0 : list.size(); if (size > 0) { // Try to reuse mCacheList to store the items of list. Object[] array; if (mCachedList == null || mCachedList.length < size) { array = new Object[size]; } else { array = mCachedList; // Clear it in case there is some reentrancy mCachedList = null; } list.toArray(array); for (int i = 0; i < size; i++) { //noinspection unchecked T item = (T) array[i]; call.call(item, animator, isReverse); array[i] = null; } // Store it for the next call so we can reuse this array, if needed. mCachedList = array; } } /** * <p>An animation listener receives notifications from an animation. * Notifications indicate animation related events, such as the end or the Loading Loading @@ -748,4 +806,29 @@ public abstract class Animator implements Cloneable { return clone; } } /** * Internally used by {@link #callOnList(ArrayList, AnimatorCaller, Object, boolean)} to * make a call on all children of a list. This can be for start, stop, pause, cancel, update, * etc notifications. * * @param <T> The type of listener to make the call on * @param <A> The type of animator that is passed as a parameter */ interface AnimatorCaller<T, A> { void call(T listener, A animator, boolean isReverse); AnimatorCaller<AnimatorListener, Animator> ON_START = AnimatorListener::onAnimationStart; AnimatorCaller<AnimatorListener, Animator> ON_END = AnimatorListener::onAnimationEnd; AnimatorCaller<AnimatorListener, Animator> ON_CANCEL = (listener, animator, isReverse) -> listener.onAnimationCancel(animator); AnimatorCaller<AnimatorListener, Animator> ON_REPEAT = (listener, animator, isReverse) -> listener.onAnimationRepeat(animator); AnimatorCaller<AnimatorPauseListener, Animator> ON_PAUSE = (listener, animator, isReverse) -> listener.onAnimationPause(animator); AnimatorCaller<AnimatorPauseListener, Animator> ON_RESUME = (listener, animator, isReverse) -> listener.onAnimationResume(animator); AnimatorCaller<ValueAnimator.AnimatorUpdateListener, ValueAnimator> ON_UPDATE = (listener, animator, isReverse) -> listener.onAnimationUpdate(animator); } }
core/java/android/animation/AnimatorSet.java +22 −27 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.function.Consumer; /** * This class plays a set of {@link Animator} objects in the specified order. Animations Loading Loading @@ -424,24 +425,28 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim throw new AndroidRuntimeException("Animators may only be run on Looper threads"); } if (isStarted()) { ArrayList<AnimatorListener> tmpListeners = null; if (mListeners != null) { tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); int size = tmpListeners.size(); for (int i = 0; i < size; i++) { tmpListeners.get(i).onAnimationCancel(this); } } ArrayList<Node> playingSet = new ArrayList<>(mPlayingSet); int setSize = playingSet.size(); for (int i = 0; i < setSize; i++) { playingSet.get(i).mAnimation.cancel(); } notifyListeners(AnimatorCaller.ON_CANCEL, false); callOnPlayingSet(Animator::cancel); mPlayingSet.clear(); endAnimation(); } } /** * Calls consumer on every Animator of mPlayingSet. * * @param consumer The method to call on every Animator of mPlayingSet. */ private void callOnPlayingSet(Consumer<Animator> consumer) { final ArrayList<Node> list = mPlayingSet; final int size = list.size(); //noinspection ForLoopReplaceableByForEach for (int i = 0; i < size; i++) { final Animator animator = list.get(i).mAnimation; consumer.accept(animator); } } // Force all the animations to end when the duration scale is 0. private void forceToEnd() { if (mEndCanBeCalled) { Loading Loading @@ -662,6 +667,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim super.pause(); if (!previouslyPaused && mPaused) { mPauseTime = -1; callOnPlayingSet(Animator::pause); } } Loading @@ -676,6 +682,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim if (mPauseTime >= 0) { addAnimationCallback(0); } callOnPlayingSet(Animator::resume); } } Loading Loading @@ -751,26 +758,14 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim private void notifyStartListeners(boolean inReverse) { if (mListeners != null && !mStartListenersCalled) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { AnimatorListener listener = tmpListeners.get(i); listener.onAnimationStart(this, inReverse); } notifyListeners(AnimatorCaller.ON_START, inReverse); } mStartListenersCalled = true; } private void notifyEndListeners(boolean inReverse) { if (mListeners != null && mStartListenersCalled) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { AnimatorListener listener = tmpListeners.get(i); listener.onAnimationEnd(this, inReverse); } notifyListeners(AnimatorCaller.ON_END, inReverse); } mStartListenersCalled = false; } Loading
core/java/android/animation/ValueAnimator.java +6 −39 Original line number Diff line number Diff line Loading @@ -1110,24 +1110,14 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio private void notifyStartListeners(boolean isReversing) { if (mListeners != null && !mStartListenersCalled) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationStart(this, isReversing); } notifyListeners(AnimatorCaller.ON_START, isReversing); } mStartListenersCalled = true; } private void notifyEndListeners(boolean isReversing) { if (mListeners != null && mStartListenersCalled) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationEnd(this, isReversing); } notifyListeners(AnimatorCaller.ON_END, isReversing); } mStartListenersCalled = false; } Loading Loading @@ -1224,15 +1214,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio // If it's not yet running, then start listeners weren't called. Call them now. notifyStartListeners(mReversing); } int listenersSize = mListeners.size(); if (listenersSize > 0) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); for (int i = 0; i < listenersSize; i++) { AnimatorListener listener = tmpListeners.get(i); listener.onAnimationCancel(this); } } notifyListeners(AnimatorCaller.ON_CANCEL, false); } endAnimation(); Loading Loading @@ -1435,12 +1417,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio done = true; } else if (newIteration && !lastIterationFinished) { // Time to repeat if (mListeners != null) { int numListeners = mListeners.size(); for (int i = 0; i < numListeners; ++i) { mListeners.get(i).onAnimationRepeat(this); } } notifyListeners(AnimatorCaller.ON_REPEAT, false); } else if (lastIterationFinished) { done = true; } Loading Loading @@ -1494,12 +1471,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio lastIteration = Math.min(lastIteration, mRepeatCount); if (iteration != lastIteration) { if (mListeners != null) { int numListeners = mListeners.size(); for (int i = 0; i < numListeners; ++i) { mListeners.get(i).onAnimationRepeat(this); } } notifyListeners(AnimatorCaller.ON_REPEAT, false); } } Loading Loading @@ -1697,12 +1669,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio for (int i = 0; i < numValues; ++i) { mValues[i].calculateValue(fraction); } if (mUpdateListeners != null) { int numListeners = mUpdateListeners.size(); for (int i = 0; i < numListeners; ++i) { mUpdateListeners.get(i).onAnimationUpdate(this); } } callOnList(mUpdateListeners, AnimatorCaller.ON_UPDATE, this, false); } @Override Loading
core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java 0 → 100644 +251 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.animation; import static org.junit.Assert.assertEquals; import android.util.PollingCheck; import android.view.View; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.filters.MediumTest; import com.android.frameworks.coretests.R; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @MediumTest public class AnimatorSetCallsTest { @Rule public final ActivityScenarioRule<AnimatorSetActivity> mRule = new ActivityScenarioRule<>(AnimatorSetActivity.class); private AnimatorSetActivity mActivity; private AnimatorSet mSet1; private CountListener mListener1; private CountListener mListener2; private CountListener mListener3; @Before public void setUp() throws Exception { mRule.getScenario().onActivity((activity) -> { mActivity = activity; View square = mActivity.findViewById(R.id.square1); mSet1 = new AnimatorSet(); mListener1 = new CountListener(); mSet1.addListener(mListener1); mSet1.addPauseListener(mListener1); AnimatorSet set2 = new AnimatorSet(); mListener2 = new CountListener(); set2.addListener(mListener2); set2.addPauseListener(mListener2); ObjectAnimator anim = ObjectAnimator.ofFloat(square, "translationX", 0f, 100f); mListener3 = new CountListener(); anim.addListener(mListener3); anim.addPauseListener(mListener3); anim.setDuration(1); set2.play(anim); mSet1.play(set2); }); } @Test public void startEndCalledOnChildren() { mRule.getScenario().onActivity((a) -> mSet1.start()); waitForOnUiThread(() -> mListener1.endForward > 0); // only startForward and endForward should have been called once mListener1.assertValues( 1, 0, 1, 0, 0, 0, 0, 0 ); mListener2.assertValues( 1, 0, 1, 0, 0, 0, 0, 0 ); mListener3.assertValues( 1, 0, 1, 0, 0, 0, 0, 0 ); } @Test public void cancelCalledOnChildren() { mRule.getScenario().onActivity((a) -> { mSet1.start(); mSet1.cancel(); }); waitForOnUiThread(() -> mListener1.endForward > 0); // only startForward and endForward should have been called once mListener1.assertValues( 1, 0, 1, 0, 1, 0, 0, 0 ); mListener2.assertValues( 1, 0, 1, 0, 1, 0, 0, 0 ); mListener3.assertValues( 1, 0, 1, 0, 1, 0, 0, 0 ); } @Test public void startEndReversedCalledOnChildren() { mRule.getScenario().onActivity((a) -> mSet1.reverse()); waitForOnUiThread(() -> mListener1.endReverse > 0); // only startForward and endForward should have been called once mListener1.assertValues( 0, 1, 0, 1, 0, 0, 0, 0 ); mListener2.assertValues( 0, 1, 0, 1, 0, 0, 0, 0 ); mListener3.assertValues( 0, 1, 0, 1, 0, 0, 0, 0 ); } @Test public void pauseResumeCalledOnChildren() { mRule.getScenario().onActivity((a) -> { mSet1.start(); mSet1.pause(); }); waitForOnUiThread(() -> mListener1.pause > 0); // only startForward and pause should have been called once mListener1.assertValues( 1, 0, 0, 0, 0, 0, 1, 0 ); mListener2.assertValues( 1, 0, 0, 0, 0, 0, 1, 0 ); mListener3.assertValues( 1, 0, 0, 0, 0, 0, 1, 0 ); mRule.getScenario().onActivity((a) -> mSet1.resume()); waitForOnUiThread(() -> mListener1.endForward > 0); // resume and endForward should have been called once mListener1.assertValues( 1, 0, 1, 0, 0, 0, 1, 1 ); mListener2.assertValues( 1, 0, 1, 0, 0, 0, 1, 1 ); mListener3.assertValues( 1, 0, 1, 0, 0, 0, 1, 1 ); } private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) { final boolean[] value = new boolean[1]; PollingCheck.waitFor(() -> { mActivity.runOnUiThread(() -> value[0] = condition.canProceed()); return value[0]; }); } private static class CountListener implements Animator.AnimatorListener, Animator.AnimatorPauseListener { public int startNoParam; public int endNoParam; public int startReverse; public int startForward; public int endForward; public int endReverse; public int cancel; public int repeat; public int pause; public int resume; public void assertValues( int startForward, int startReverse, int endForward, int endReverse, int cancel, int repeat, int pause, int resume ) { assertEquals(0, startNoParam); assertEquals(0, endNoParam); assertEquals(startForward, this.startForward); assertEquals(startReverse, this.startReverse); assertEquals(endForward, this.endForward); assertEquals(endReverse, this.endReverse); assertEquals(cancel, this.cancel); assertEquals(repeat, this.repeat); assertEquals(pause, this.pause); assertEquals(resume, this.resume); } @Override public void onAnimationStart(Animator animation, boolean isReverse) { if (isReverse) { startReverse++; } else { startForward++; } } @Override public void onAnimationEnd(Animator animation, boolean isReverse) { if (isReverse) { endReverse++; } else { endForward++; } } @Override public void onAnimationStart(Animator animation) { startNoParam++; } @Override public void onAnimationEnd(Animator animation) { endNoParam++; } @Override public void onAnimationCancel(Animator animation) { cancel++; } @Override public void onAnimationRepeat(Animator animation) { repeat++; } @Override public void onAnimationPause(Animator animation) { pause++; } @Override public void onAnimationResume(Animator animation) { resume++; } } }