Change cancel/end behavior of animations to be synchronous
Previously, cancel() and end() calls would simply log a message to
be handled later by the animation handler. This caused problems with
coordinating complex animations, where some start() events for
future animations would occur before end() events for animations already
completed.
The change is to make these events synchronous (and require them to be
called from the appropriate thread), simplifying the code and the usage.
Also, fixed various timing and event bugs in AnimatorSet, and removed
the getter/setter properties from ObjectAnimator, since an earlier change
makes these properties undesirable (because the code will use a faster
JNI approach instead of reflection when it can).
Change-Id: I05c16645c2a31a92048a6031ddb126eb4312a946
diff --git a/api/current.xml b/api/current.xml
index f02ec65..708a852 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -20161,17 +20161,6 @@
visibility="public"
>
</method>
-<method name="getGetter"
- return="java.lang.reflect.Method"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
<method name="getPropertyName"
return="java.lang.String"
abstract="false"
@@ -20183,17 +20172,6 @@
visibility="public"
>
</method>
-<method name="getSetter"
- return="java.lang.reflect.Method"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
<method name="ofFloat"
return="android.animation.PropertyValuesHolder"
abstract="false"
@@ -20282,19 +20260,6 @@
<parameter name="values" type="float...">
</parameter>
</method>
-<method name="setGetter"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="getter" type="java.lang.reflect.Method">
-</parameter>
-</method>
<method name="setIntValues"
return="void"
abstract="false"
@@ -20347,19 +20312,6 @@
<parameter name="propertyName" type="java.lang.String">
</parameter>
</method>
-<method name="setSetter"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="setter" type="java.lang.reflect.Method">
-</parameter>
-</method>
</class>
<class name="RGBEvaluator"
extends="java.lang.Object"
@@ -250407,7 +250359,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="arg0" type="T">
+<parameter name="t" type="T">
</parameter>
</method>
</interface>
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 22e04a7..5fe3644 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -36,22 +36,36 @@
* this call, because all animation events are posted to a central timing loop so that animation
* times are all synchronized on a single timing pulse on the UI thread. So the animation will
* start the next time that event handler processes events.
+ *
+ * <p>The animation started by calling this method will be run on the thread that called
+ * this method. This thread should have a Looper on it (a runtime exception will be thrown if
+ * this is not the case). Also, if the animation will animate
+ * properties of objects in the view hierarchy, then the calling thread should be the UI
+ * thread for that view hierarchy.</p>
+ *
*/
public void start() {
}
/**
* Cancels the animation. Unlike {@link #end()}, <code>cancel()</code> causes the animation to
- * stop in its tracks, sending an {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to
- * its listeners, followed by an {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message.
+ * stop in its tracks, sending an
+ * {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to
+ * its listeners, followed by an
+ * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message.
+ *
+ * <p>This method must be called on the thread that is running the animation.</p>
*/
public void cancel() {
}
/**
* Ends the animation. This causes the animation to assign the end value of the property being
- * animated, then calling the {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on
+ * animated, then calling the
+ * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on
* its listeners.
+ *
+ * <p>This method must be called on the thread that is running the animation.</p>
*/
public void end() {
}
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 154e084..d77dbdc 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -96,6 +96,9 @@
// The amount of time in ms to delay starting the animation after start() is called
private long mStartDelay = 0;
+ // Animator used for a nonzero startDelay
+ private ValueAnimator mDelayAnim = null;
+
// How long the child animations should last in ms. The default value is negative, which
// simply means that there is no duration set on the AnimatorSet. When a real duration is
@@ -276,6 +279,19 @@
listener.onAnimationCancel(this);
}
}
+ if (mDelayAnim != null && mDelayAnim.isRunning()) {
+ // If we're currently in the startDelay period, just cancel that animator and
+ // send out the end event to all listeners
+ mDelayAnim.cancel();
+ if (mListeners != null) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ for (AnimatorListener listener : tmpListeners) {
+ listener.onAnimationEnd(this);
+ }
+ }
+ return;
+ }
if (mSortedNodes.size() > 0) {
for (Node node : mSortedNodes) {
node.animation.cancel();
@@ -302,6 +318,9 @@
node.animation.addListener(mSetListener);
}
}
+ if (mDelayAnim != null) {
+ mDelayAnim.cancel();
+ }
if (mSortedNodes.size() > 0) {
for (Node node : mSortedNodes) {
node.animation.end();
@@ -411,12 +430,25 @@
// contains the animation nodes in the correct order.
sortNodes();
+ int numSortedNodes = mSortedNodes.size();
+ for (int i = 0; i < numSortedNodes; ++i) {
+ Node node = mSortedNodes.get(i);
+ // First, clear out the old listeners
+ ArrayList<AnimatorListener> oldListeners = node.animation.getListeners();
+ if (oldListeners != null && oldListeners.size() > 0) {
+ for (AnimatorListener listener : oldListeners) {
+ if (listener instanceof DependencyListener) {
+ node.animation.removeListener(listener);
+ }
+ }
+ }
+ }
+
// nodesToStart holds the list of nodes to be started immediately. We don't want to
// start the animations in the loop directly because we first need to set up
// dependencies on all of the nodes. For example, we don't want to start an animation
// when some other animation also wants to start when the first animation begins.
final ArrayList<Node> nodesToStart = new ArrayList<Node>();
- int numSortedNodes = mSortedNodes.size();
for (int i = 0; i < numSortedNodes; ++i) {
Node node = mSortedNodes.get(i);
if (mSetListener == null) {
@@ -443,19 +475,25 @@
}
} else {
// TODO: Need to cancel out of the delay appropriately
- ValueAnimator delayAnim = ValueAnimator.ofFloat(0f, 1f);
- delayAnim.setDuration(mStartDelay);
- delayAnim.addListener(new AnimatorListenerAdapter() {
+ mDelayAnim = ValueAnimator.ofFloat(0f, 1f);
+ mDelayAnim.setDuration(mStartDelay);
+ mDelayAnim.addListener(new AnimatorListenerAdapter() {
+ boolean canceled = false;
+ public void onAnimationCancel(Animator anim) {
+ canceled = true;
+ }
public void onAnimationEnd(Animator anim) {
- int numNodes = nodesToStart.size();
- for (int i = 0; i < numNodes; ++i) {
- Node node = nodesToStart.get(i);
- node.animation.start();
- mPlayingSet.add(node.animation);
+ if (!canceled) {
+ int numNodes = nodesToStart.size();
+ for (int i = 0; i < numNodes; ++i) {
+ Node node = nodesToStart.get(i);
+ node.animation.start();
+ mPlayingSet.add(node.animation);
+ }
}
}
});
- delayAnim.start();
+ mDelayAnim.start();
}
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 7c2e70d..7f11871 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -19,6 +19,7 @@
import android.util.Log;
import java.lang.reflect.Method;
+import java.util.ArrayList;
/**
* This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
@@ -31,6 +32,7 @@
*
*/
public final class ObjectAnimator extends ValueAnimator {
+ private static final boolean DBG = false;
// The target object on which the property exists, set in the constructor
private Object mTarget;
@@ -265,6 +267,21 @@
}
}
+ @Override
+ public void start() {
+ if (DBG) {
+ Log.d("ObjectAnimator", "Anim target, duration" + mTarget + ", " + getDuration());
+ for (int i = 0; i < mValues.length; ++i) {
+ PropertyValuesHolder pvh = mValues[i];
+ ArrayList<Keyframe> keyframes = pvh.mKeyframeSet.mKeyframes;
+ Log.d("ObjectAnimator", " Values[" + i + "]: " +
+ pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " +
+ keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue());
+ }
+ }
+ super.start();
+ }
+
/**
* This function is called immediately before processing the first animation
* frame of an animation. If there is a nonzero <code>startDelay</code>, the
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index 97aa5a1..286c03b 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -544,67 +544,6 @@
}
/**
- * Sets the <code>Method</code> that is called with the animated values calculated
- * during the animation. Setting the setter method is an alternative to supplying a
- * {@link #setPropertyName(String) propertyName} from which the method is derived. This
- * approach is more direct, and is especially useful when a function must be called that does
- * not correspond to the convention of <code>setName()</code>. For example, if a function
- * called <code>offset()</code> is to be called with the animated values, there is no way
- * to tell <code>ObjectAnimator</code> how to call that function simply through a property
- * name, so a setter method should be supplied instead.
- *
- * <p>Note that the setter function must take the same parameter type as the
- * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
- * the setter function will fail.</p>
- *
- * @param setter The setter method that should be called with the animated values.
- */
- public void setSetter(Method setter) {
- mSetter = setter;
- }
-
- /**
- * Gets the <code>Method</code> that is called with the animated values calculated
- * during the animation.
- */
- public Method getSetter() {
- return mSetter;
- }
-
- /**
- * Sets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
- * <code>valueTo</code> properties. Setting the getter method is an alternative to supplying a
- * {@link #setPropertyName(String) propertyName} from which the method is derived. This
- * approach is more direct, and is especially useful when a function must be called that does
- * not correspond to the convention of <code>setName()</code>. For example, if a function
- * called <code>offset()</code> is to be called to get an initial value, there is no way
- * to tell <code>ObjectAnimator</code> how to call that function simply through a property
- * name, so a getter method should be supplied instead.
- *
- * <p>Note that the getter method is only called whether supplied here or derived
- * from the property name, if one of <code>valueFrom</code> or <code>valueTo</code> are
- * null. If both of those values are non-null, then there is no need to get one of the
- * values and the getter is not called.
- *
- * <p>Note that the getter function must return the same parameter type as the
- * <code>valueFrom</code> and <code>valueTo</code> properties (whichever of them are
- * non-null), otherwise the call to the getter function will fail.</p>
- *
- * @param getter The getter method that should be called to get initial animation values.
- */
- public void setGetter(Method getter) {
- mGetter = getter;
- }
-
- /**
- * Gets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
- * <code>valueTo</code> properties.
- */
- public Method getGetter() {
- return mGetter;
- }
-
- /**
* Sets the name of the property that will be animated. This name is used to derive
* a setter function that will be called to set animated values.
* For example, a property name of <code>foo</code> will result
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 8e541c2..1542c49 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -62,9 +62,7 @@
*/
private static final int STOPPED = 0; // Not yet playing
private static final int RUNNING = 1; // Playing normally
- private static final int CANCELED = 2; // cancel() called - need to end it
- private static final int ENDED = 3; // end() called - need to end it
- private static final int SEEKED = 4; // Seeked to some time value
+ private static final int SEEKED = 2; // Seeked to some time value
/**
* Internal variables
@@ -178,7 +176,7 @@
* Flag that represents the current state of the animation. Used to figure out when to start
* an animation (if state == STOPPED). Also used to end an animation that
* has been cancel()'d or end()'d since the last animation frame. Possible values are
- * STOPPED, RUNNING, ENDED, CANCELED.
+ * STOPPED, RUNNING, SEEKED.
*/
private int mPlayingState = STOPPED;
@@ -581,8 +579,7 @@
for (int i = 0; i < count; ++i) {
ValueAnimator anim = pendingCopy.get(i);
// If the animation has a startDelay, place it on the delayed list
- if (anim.mStartDelay == 0 || anim.mPlayingState == ENDED ||
- anim.mPlayingState == CANCELED) {
+ if (anim.mStartDelay == 0) {
anim.startAnimation();
} else {
delayedAnims.add(anim);
@@ -619,14 +616,28 @@
// Now process all active animations. The return value from animationFrame()
// tells the handler whether it should now be ended
int numAnims = animations.size();
- for (int i = 0; i < numAnims; ++i) {
+ int i = 0;
+ while (i < numAnims) {
ValueAnimator anim = animations.get(i);
if (anim.animationFrame(currentTime)) {
endingAnims.add(anim);
}
+ if (animations.size() == numAnims) {
+ ++i;
+ } else {
+ // An animation might be canceled or ended by client code
+ // during the animation frame. Check to see if this happened by
+ // seeing whether the current index is the same as it was before
+ // calling animationFrame(). Another approach would be to copy
+ // animations to a temporary list and process that list instead,
+ // but that entails garbage and processing overhead that would
+ // be nice to avoid.
+ --numAnims;
+ endingAnims.remove(anim);
+ }
}
if (endingAnims.size() > 0) {
- for (int i = 0; i < endingAnims.size(); ++i) {
+ for (i = 0; i < endingAnims.size(); ++i) {
endingAnims.get(i).endAnimation();
}
endingAnims.clear();
@@ -918,9 +929,7 @@
// to run
if (mPlayingState != STOPPED || sPendingAnimations.get().contains(this) ||
sDelayedAnims.get().contains(this)) {
- // Just set the CANCELED flag - this causes the animation to end the next time a frame
- // is processed.
- mPlayingState = CANCELED;
+ endAnimation();
}
}
@@ -929,23 +938,21 @@
if (!sAnimations.get().contains(this) && !sPendingAnimations.get().contains(this)) {
// Special case if the animation has not yet started; get it ready for ending
mStartedDelay = false;
- sPendingAnimations.get().add(this);
- AnimationHandler animationHandler = sAnimationHandler.get();
- if (animationHandler == null) {
- animationHandler = new AnimationHandler();
- sAnimationHandler.set(animationHandler);
- }
- animationHandler.sendEmptyMessage(ANIMATION_START);
+ startAnimation();
}
- // Just set the ENDED flag - this causes the animation to end the next time a frame
- // is processed.
- mPlayingState = ENDED;
+ // The final value set on the target varies, depending on whether the animation
+ // was supposed to repeat an odd number of times
+ if (mRepeatCount > 0 && (mRepeatCount & 0x01) == 1) {
+ animateValue(0f);
+ } else {
+ animateValue(1f);
+ }
+ endAnimation();
}
@Override
public boolean isRunning() {
- // ENDED or CANCELED indicate that it has been ended or canceled, but not processed yet
- return (mPlayingState == RUNNING || mPlayingState == ENDED || mPlayingState == CANCELED);
+ return (mPlayingState == RUNNING);
}
/**
@@ -973,6 +980,8 @@
*/
private void endAnimation() {
sAnimations.get().remove(this);
+ sPendingAnimations.get().remove(this);
+ sDelayedAnims.get().remove(this);
mPlayingState = STOPPED;
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
@@ -1014,10 +1023,6 @@
* should be added to the set of active animations.
*/
private boolean delayedAnimationFrame(long currentTime) {
- if (mPlayingState == CANCELED || mPlayingState == ENDED) {
- // end the delay, process an animation frame to actually cancel it
- return true;
- }
if (!mStartedDelay) {
mStartedDelay = true;
mDelayStartTime = currentTime;
@@ -1088,19 +1093,6 @@
}
animateValue(fraction);
break;
- case ENDED:
- // The final value set on the target varies, depending on whether the animation
- // was supposed to repeat an odd number of times
- if (mRepeatCount > 0 && (mRepeatCount & 0x01) == 1) {
- animateValue(0f);
- } else {
- animateValue(1f);
- }
- // Fall through to set done flag
- case CANCELED:
- done = true;
- mPlayingState = STOPPED;
- break;
}
return done;
diff --git a/core/java/com/android/internal/widget/DrawableHolder.java b/core/java/com/android/internal/widget/DrawableHolder.java
index a528aeb..947e0f3 100644
--- a/core/java/com/android/internal/widget/DrawableHolder.java
+++ b/core/java/com/android/internal/widget/DrawableHolder.java
@@ -87,15 +87,12 @@
* @param property
*/
public void removeAnimationFor(String property) {
- ArrayList<ObjectAnimator> removalList = new ArrayList<ObjectAnimator>();
- for (ObjectAnimator currentAnim : mAnimators) {
+ ArrayList<ObjectAnimator> removalList = (ArrayList<ObjectAnimator>)mAnimators.clone();
+ for (ObjectAnimator currentAnim : removalList) {
if (property.equals(currentAnim.getPropertyName())) {
currentAnim.cancel();
- removalList.add(currentAnim);
}
}
- if (DBG) Log.v(TAG, "Remove list size: " + removalList.size());
- mAnimators.removeAll(removalList);
}
/**