From a18a86b43e40e3c15dcca0ae0148d641be9b25fe Mon Sep 17 00:00:00 2001 From: Chet Haase Date: Tue, 7 Sep 2010 13:20:00 -0700 Subject: Rename several animation classes Change-Id: I6a4544875090db485163c8d56de8718f56d267c7 --- core/java/android/animation/Animatable.java | 248 ------ .../java/android/animation/AnimatableInflater.java | 269 ------ .../animation/AnimatableListenerAdapter.java | 56 -- core/java/android/animation/Animator.java | 969 +++----------------- core/java/android/animation/AnimatorInflater.java | 269 ++++++ .../android/animation/AnimatorListenerAdapter.java | 54 ++ core/java/android/animation/AnimatorSet.java | 939 ++++++++++++++++++++ core/java/android/animation/Keyframe.java | 6 +- core/java/android/animation/KeyframeSet.java | 2 +- core/java/android/animation/LayoutTransition.java | 104 +-- core/java/android/animation/ObjectAnimator.java | 246 ++++++ core/java/android/animation/PropertyAnimator.java | 246 ------ .../android/animation/PropertyValuesHolder.java | 50 +- core/java/android/animation/Sequencer.java | 943 -------------------- core/java/android/animation/TypeEvaluator.java | 4 +- core/java/android/animation/ValueAnimator.java | 972 +++++++++++++++++++++ core/java/android/app/Fragment.java | 5 +- core/java/android/app/FragmentManager.java | 56 +- .../android/view/animation/AnimationUtils.java | 2 +- core/java/android/widget/AdapterViewAnimator.java | 10 +- core/java/android/widget/StackView.java | 18 +- 21 files changed, 2729 insertions(+), 2739 deletions(-) delete mode 100644 core/java/android/animation/Animatable.java delete mode 100644 core/java/android/animation/AnimatableInflater.java delete mode 100644 core/java/android/animation/AnimatableListenerAdapter.java mode change 100755 => 100644 core/java/android/animation/Animator.java create mode 100644 core/java/android/animation/AnimatorInflater.java create mode 100644 core/java/android/animation/AnimatorListenerAdapter.java create mode 100644 core/java/android/animation/AnimatorSet.java create mode 100644 core/java/android/animation/ObjectAnimator.java delete mode 100644 core/java/android/animation/PropertyAnimator.java delete mode 100644 core/java/android/animation/Sequencer.java create mode 100755 core/java/android/animation/ValueAnimator.java (limited to 'core/java/android') diff --git a/core/java/android/animation/Animatable.java b/core/java/android/animation/Animatable.java deleted file mode 100644 index 3fdf200..0000000 --- a/core/java/android/animation/Animatable.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2010 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 android.view.animation.Interpolator; - -import java.util.ArrayList; - -/** - * This is the superclass for classes which provide basic support for animations which can be - * started, ended, and have AnimatableListeners added to them. - */ -public abstract class Animatable implements Cloneable { - - - /** - * The set of listeners to be sent events through the life of an animation. - */ - ArrayList mListeners = null; - - /** - * Starts this animation. If the animation has a nonzero startDelay, the animation will start - * running after that delay elapses. Note that the animation does not start synchronously with - * 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. - */ - public void start() { - } - - /** - * Cancels the animation. Unlike {@link #end()}, cancel() causes the animation to - * stop in its tracks, sending an {@link AnimatableListener#onAnimationCancel(Animatable)} to - * its listeners, followed by an {@link AnimatableListener#onAnimationEnd(Animatable)} message. - */ - public void cancel() { - } - - /** - * Ends the animation. This causes the animation to assign the end value of the property being - * animated, then calling the {@link AnimatableListener#onAnimationEnd(Animatable)} method on - * its listeners. - */ - public void end() { - } - - /** - * The amount of time, in milliseconds, to delay starting the animation after - * {@link #start()} is called. - * - * @return the number of milliseconds to delay running the animation - */ - public abstract long getStartDelay(); - - /** - * The amount of time, in milliseconds, to delay starting the animation after - * {@link #start()} is called. - - * @param startDelay The amount of the delay, in milliseconds - */ - public abstract void setStartDelay(long startDelay); - - - /** - * Sets the length of the animation. - * - * @param duration The length of the animation, in milliseconds. - */ - public abstract void setDuration(long duration); - - /** - * Gets the length of the animation. - * - * @return The length of the animation, in milliseconds. - */ - public abstract long getDuration(); - - /** - * The time interpolator used in calculating the elapsed fraction of this animation. The - * interpolator determines whether the animation runs with linear or non-linear motion, - * such as acceleration and deceleration. The default value is - * {@link android.view.animation.AccelerateDecelerateInterpolator} - * - * @param value the interpolator to be used by this animation - */ - public abstract void setInterpolator(Interpolator value); - - /** - * Returns whether this Animatable is currently running (having been started and not yet ended). - * @return Whether the Animatable is running. - */ - public abstract boolean isRunning(); - - /** - * Adds a listener to the set of listeners that are sent events through the life of an - * animation, such as start, repeat, and end. - * - * @param listener the listener to be added to the current set of listeners for this animation. - */ - public void addListener(AnimatableListener listener) { - if (mListeners == null) { - mListeners = new ArrayList(); - } - mListeners.add(listener); - } - - /** - * Removes a listener from the set listening to this animation. - * - * @param listener the listener to be removed from the current set of listeners for this - * animation. - */ - public void removeListener(AnimatableListener listener) { - if (mListeners == null) { - return; - } - mListeners.remove(listener); - if (mListeners.size() == 0) { - mListeners = null; - } - } - - /** - * Gets the set of {@link AnimatableListener} objects that are currently - * listening for events on this Animatable object. - * - * @return ArrayList The set of listeners. - */ - public ArrayList getListeners() { - return mListeners; - } - - /** - * Removes all listeners from this object. This is equivalent to calling - * getListeners() followed by calling clear() on the - * returned list of listeners. - */ - public void removeAllListeners() { - if (mListeners != null) { - mListeners.clear(); - mListeners = null; - } - } - - @Override - public Animatable clone() { - try { - final Animatable anim = (Animatable) super.clone(); - if (mListeners != null) { - ArrayList oldListeners = mListeners; - anim.mListeners = new ArrayList(); - int numListeners = oldListeners.size(); - for (int i = 0; i < numListeners; ++i) { - anim.mListeners.add(oldListeners.get(i)); - } - } - return anim; - } catch (CloneNotSupportedException e) { - throw new AssertionError(); - } - } - - /** - * This method tells the object to use appropriate information to extract - * starting values for the animation. For example, a Sequencer object will pass - * this call to its child objects to tell them to set up the values. A - * PropertyAnimator object will use the information it has about its target object - * and PropertyValuesHolder objects to get the start values for its properties. - * An Animator object will ignore the request since it does not have enough - * information (such as a target object) to gather these values. - */ - public void setupStartValues() { - } - - /** - * This method tells the object to use appropriate information to extract - * ending values for the animation. For example, a Sequencer object will pass - * this call to its child objects to tell them to set up the values. A - * PropertyAnimator object will use the information it has about its target object - * and PropertyValuesHolder objects to get the start values for its properties. - * An Animator object will ignore the request since it does not have enough - * information (such as a target object) to gather these values. - */ - public void setupEndValues() { - } - - /** - * Sets the target object whose property will be animated by this animation. Not all subclasses - * operate on target objects (for example, {@link android.animation.Animator}, but this method - * is on the superclass for the convenience of dealing generically with those subclasses - * that do handle targets. - * - * @param target The object being animated - */ - public void setTarget(Object target) { - } - - /** - *

An animation listener receives notifications from an animation. - * Notifications indicate animation related events, such as the end or the - * repetition of the animation.

- */ - public static interface AnimatableListener { - /** - *

Notifies the start of the animation.

- * - * @param animation The started animation. - */ - void onAnimationStart(Animatable animation); - - /** - *

Notifies the end of the animation. This callback is not invoked - * for animations with repeat count set to INFINITE.

- * - * @param animation The animation which reached its end. - */ - void onAnimationEnd(Animatable animation); - - /** - *

Notifies the cancellation of the animation. This callback is not invoked - * for animations with repeat count set to INFINITE.

- * - * @param animation The animation which was canceled. - */ - void onAnimationCancel(Animatable animation); - - /** - *

Notifies the repetition of the animation.

- * - * @param animation The animation which was repeated. - */ - void onAnimationRepeat(Animatable animation); - } -} diff --git a/core/java/android/animation/AnimatableInflater.java b/core/java/android/animation/AnimatableInflater.java deleted file mode 100644 index 88fa77e..0000000 --- a/core/java/android/animation/AnimatableInflater.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) 2010 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 android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.content.res.Resources.NotFoundException; -import android.util.AttributeSet; -import android.util.Xml; -import android.view.animation.AnimationUtils; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.ArrayList; - -/** - * This class is used to instantiate menu XML files into Animatable objects. - *

- * For performance reasons, menu inflation relies heavily on pre-processing of - * XML files that is done at build time. Therefore, it is not currently possible - * to use MenuInflater with an XmlPullParser over a plain XML file at runtime; - * it only works with an XmlPullParser returned from a compiled resource (R. - * something file.) - */ -public class AnimatableInflater { - - /** - * These flags are used when parsing Sequencer objects - */ - private static final int TOGETHER = 0; - private static final int SEQUENTIALLY = 1; - - /** - * Enum values used in XML attributes to indicate the value for mValueType - */ - private static final int VALUE_TYPE_FLOAT = 0; - private static final int VALUE_TYPE_INT = 1; - private static final int VALUE_TYPE_DOUBLE = 2; - private static final int VALUE_TYPE_COLOR = 3; - private static final int VALUE_TYPE_CUSTOM = 4; - - /** - * Loads an {@link Animatable} object from a resource - * - * @param context Application context used to access resources - * @param id The resource id of the animation to load - * @return The animatable object reference by the specified id - * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded - */ - public static Animatable loadAnimatable(Context context, int id) - throws NotFoundException { - - XmlResourceParser parser = null; - try { - parser = context.getResources().getAnimation(id); - return createAnimatableFromXml(context, parser); - } catch (XmlPullParserException ex) { - Resources.NotFoundException rnf = - new Resources.NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; - } catch (IOException ex) { - Resources.NotFoundException rnf = - new Resources.NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; - } finally { - if (parser != null) parser.close(); - } - } - - private static Animatable createAnimatableFromXml(Context c, XmlPullParser parser) - throws XmlPullParserException, IOException { - - return createAnimatableFromXml(c, parser, Xml.asAttributeSet(parser), null, 0); - } - - private static Animatable createAnimatableFromXml(Context c, XmlPullParser parser, - AttributeSet attrs, Sequencer parent, int sequenceOrdering) - throws XmlPullParserException, IOException { - - Animatable anim = null; - ArrayList childAnims = null; - - // Make sure we are on a start tag. - int type; - int depth = parser.getDepth(); - - while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) - && type != XmlPullParser.END_DOCUMENT) { - - if (type != XmlPullParser.START_TAG) { - continue; - } - - String name = parser.getName(); - - if (name.equals("property")) { - anim = loadPropertyAnimator(c, attrs); - } else if (name.equals("animator")) { - anim = loadAnimator(c, attrs, null); - } else if (name.equals("sequencer")) { - anim = new Sequencer(); - TypedArray a = c.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.Sequencer); - int ordering = a.getInt(com.android.internal.R.styleable.Sequencer_ordering, - TOGETHER); - createAnimatableFromXml(c, parser, attrs, (Sequencer) anim, ordering); - a.recycle(); - } else { - throw new RuntimeException("Unknown animator name: " + parser.getName()); - } - - if (parent != null) { - if (childAnims == null) { - childAnims = new ArrayList(); - } - childAnims.add(anim); - } - } - if (parent != null && childAnims != null) { - Animatable[] animsArray = new Animatable[childAnims.size()]; - int index = 0; - for (Animatable a : childAnims) { - animsArray[index++] = a; - } - if (sequenceOrdering == TOGETHER) { - parent.playTogether(animsArray); - } else { - parent.playSequentially(animsArray); - } - } - - return anim; - - } - - private static PropertyAnimator loadPropertyAnimator(Context context, AttributeSet attrs) - throws NotFoundException { - - PropertyAnimator anim = new PropertyAnimator(); - - loadAnimator(context, attrs, anim); - - TypedArray a = - context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.PropertyAnimator); - - String propertyName = a.getString(com.android.internal.R.styleable.PropertyAnimator_propertyName); - - anim.setPropertyName(propertyName); - - a.recycle(); - - return anim; - } - - /** - * Creates a new animation whose parameters come from the specified context and - * attributes set. - * - * @param context the application environment - * @param attrs the set of attributes holding the animation parameters - */ - private static Animator loadAnimator(Context context, AttributeSet attrs, Animator anim) - throws NotFoundException { - - TypedArray a = - context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator); - - long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 0); - - long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0); - - int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType, - VALUE_TYPE_FLOAT); - - Object valueFrom = null; - Object valueTo = null; - TypeEvaluator evaluator = null; - - switch (valueType) { - case VALUE_TYPE_FLOAT: - if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) { - valueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f); - } - if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) { - valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f); - } - break; - case VALUE_TYPE_COLOR: - evaluator = new RGBEvaluator(); - // fall through to pick up values - case VALUE_TYPE_INT: - if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) { - valueFrom = a.getInteger(com.android.internal.R.styleable.Animator_valueFrom, 0); - } - if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) { - valueTo = a.getInteger(com.android.internal.R.styleable.Animator_valueTo, 0); - } - break; - case VALUE_TYPE_DOUBLE: - if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) { - valueFrom = (Double)((Float)(a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f))).doubleValue(); - } - if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) { - valueTo = (Double)((Float)a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f)).doubleValue(); - } - break; - case VALUE_TYPE_CUSTOM: - // TODO: How to get an 'Object' value? - if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) { - valueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f); - } - if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) { - valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f); - } - break; - } - - if (anim == null) { - anim = new Animator(duration, valueFrom, valueTo); - } else { - anim.setDuration(duration); - anim.setValues(valueFrom, valueTo); - } - - anim.setStartDelay(startDelay); - - if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) { - anim.setRepeatCount( - a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0)); - } - if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) { - anim.setRepeatMode( - a.getInt(com.android.internal.R.styleable.Animator_repeatMode, - Animator.RESTART)); - } - if (evaluator != null) { - anim.setEvaluator(evaluator); - } - - final int resID = - a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0); - if (resID > 0) { - anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID)); - } - a.recycle(); - - return anim; - } -} diff --git a/core/java/android/animation/AnimatableListenerAdapter.java b/core/java/android/animation/AnimatableListenerAdapter.java deleted file mode 100644 index c169b28..0000000 --- a/core/java/android/animation/AnimatableListenerAdapter.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2010 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 android.animation.Animatable.AnimatableListener; - -/** - * This adapter class provides empty implementations of the methods from {@link AnimatableListener}. - * Any custom listener that cares only about a subset of the methods of this listener can - * simply subclass this adapter class instead of implementing the interface directly. - */ -public abstract class AnimatableListenerAdapter implements AnimatableListener { - - /** - * {@inheritdoc} - */ - @Override - public void onAnimationCancel(Animatable animation) { - } - - /** - * {@inheritdoc} - */ - @Override - public void onAnimationEnd(Animatable animation) { - } - - /** - * {@inheritdoc} - */ - @Override - public void onAnimationRepeat(Animatable animation) { - } - - /** - * {@inheritdoc} - */ - @Override - public void onAnimationStart(Animatable animation) { - } - -} diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java old mode 100755 new mode 100644 index 8e947ec..2ada6d6 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -16,461 +16,46 @@ package android.animation; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import java.util.ArrayList; -import java.util.HashMap; /** - * This class provides a simple timing engine for running animations - * which calculate animated values and set them on target objects. - * - *

There is a single timing pulse that all animations use. It runs in a - * custom handler to ensure that property changes happen on the UI thread.

- * - *

By default, Animator uses non-linear time interpolation, via the - * {@link AccelerateDecelerateInterpolator} class, which accelerates into and decelerates - * out of an animation. This behavior can be changed by calling - * {@link Animator#setInterpolator(Interpolator)}.

+ * This is the superclass for classes which provide basic support for animations which can be + * started, ended, and have AnimatorListeners added to them. */ -public class Animator extends Animatable { - - /** - * Internal constants - */ - - /* - * The default amount of time in ms between animation frames - */ - private static final long DEFAULT_FRAME_DELAY = 30; - - /** - * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent - * by the handler to itself to process the next animation frame - */ - private static final int ANIMATION_START = 0; - private static final int ANIMATION_FRAME = 1; - - /** - * Values used with internal variable mPlayingState to indicate the current state of an - * animation. - */ - 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 - - /** - * Internal variables - * NOTE: This object implements the clone() method, making a deep copy of any referenced - * objects. As other non-trivial fields are added to this class, make sure to add logic - * to clone() to make deep copies of them. - */ - - // The first time that the animation's animateFrame() method is called. This time is used to - // determine elapsed time (and therefore the elapsed fraction) in subsequent calls - // to animateFrame() - private long mStartTime; - - /** - * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked - * to a value. - */ - private long mSeekTime = -1; - - // The static sAnimationHandler processes the internal timing loop on which all animations - // are based - private static AnimationHandler sAnimationHandler; - - // The static list of all active animations - private static final ArrayList sAnimations = new ArrayList(); - - // The set of animations to be started on the next animation frame - private static final ArrayList sPendingAnimations = new ArrayList(); - - // The time interpolator to be used if none is set on the animation - private static final Interpolator sDefaultInterpolator = new AccelerateDecelerateInterpolator(); - - // type evaluators for the three primitive types handled by this implementation - private static final TypeEvaluator sIntEvaluator = new IntEvaluator(); - private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator(); - private static final TypeEvaluator sDoubleEvaluator = new DoubleEvaluator(); - - /** - * Used to indicate whether the animation is currently playing in reverse. This causes the - * elapsed fraction to be inverted to calculate the appropriate values. - */ - private boolean mPlayingBackwards = false; - - /** - * This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the - * repeatCount (if repeatCount!=INFINITE), the animation ends - */ - private int mCurrentIteration = 0; - - /** - * Tracks whether a startDelay'd animation has begun playing through the startDelay. - */ - private boolean mStartedDelay = false; - - /** - * Tracks the time at which the animation began playing through its startDelay. This is - * different from the mStartTime variable, which is used to track when the animation became - * active (which is when the startDelay expired and the animation was added to the active - * animations list). - */ - private long mDelayStartTime; - - /** - * 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. - */ - private int mPlayingState = STOPPED; - - /** - * Internal collections used to avoid set collisions as animations start and end while being - * processed. - */ - private static final ArrayList sEndingAnims = new ArrayList(); - private static final ArrayList sDelayedAnims = new ArrayList(); - private static final ArrayList sReadyAnims = new ArrayList(); - - /** - * Flag that denotes whether the animation is set up and ready to go. Used to - * set up animation that has not yet been started. - */ - boolean mInitialized = false; - - // - // Backing variables - // - - // How long the animation should last in ms - private long mDuration; - - // The amount of time in ms to delay starting the animation after start() is called - private long mStartDelay = 0; - - // The number of milliseconds between animation frames - private static long sFrameDelay = DEFAULT_FRAME_DELAY; +public abstract class Animator implements Cloneable { - // The number of times the animation will repeat. The default is 0, which means the animation - // will play only once - private int mRepeatCount = 0; - - /** - * The type of repetition that will occur when repeatMode is nonzero. RESTART means the - * animation will start from the beginning on every new cycle. REVERSE means the animation - * will reverse directions on each iteration. - */ - private int mRepeatMode = RESTART; - - /** - * The time interpolator to be used. The elapsed fraction of the animation will be passed - * through this interpolator to calculate the interpolated fraction, which is then used to - * calculate the animated values. - */ - private Interpolator mInterpolator = sDefaultInterpolator; /** * The set of listeners to be sent events through the life of an animation. */ - private ArrayList mUpdateListeners = null; - - /** - * The property/value sets being animated. - */ - PropertyValuesHolder[] mValues; - - /** - * A hashmap of the PropertyValuesHolder objects. This map is used to lookup animated values - * by property name during calls to getAnimatedValue(String). - */ - HashMap mValuesMap; - - /** - * Public constants - */ - - /** - * When the animation reaches the end and repeatCount is INFINITE - * or a positive value, the animation restarts from the beginning. - */ - public static final int RESTART = 1; - /** - * When the animation reaches the end and repeatCount is INFINITE - * or a positive value, the animation reverses direction on every iteration. - */ - public static final int REVERSE = 2; - /** - * This value used used with the {@link #setRepeatCount(int)} property to repeat - * the animation indefinitely. - */ - public static final int INFINITE = -1; - - /** - * Creates a new Animator object. This default constructor is primarily for - * use internally; the other constructors which take parameters are more generally - * useful. - */ - public Animator() { - } - - /** - * Constructs an Animator object with the specified duration and set of - * values. If the values are a set of PropertyValuesHolder objects, then these objects - * define the potentially multiple properties being animated and the values the properties are - * animated between. Otherwise, the values define a single set of values animated between. - * - * @param duration The length of the animation, in milliseconds. - * @param values The set of values to animate between. If these values are not - * PropertyValuesHolder objects, then there should be more than one value, since the values - * determine the interval to animate between. - */ - public Animator(long duration, T...values) { - mDuration = duration; - if (values.length > 0) { - setValues(values); - } - } - - /** - * Sets the values, per property, being animated between. This function is called internally - * by the constructors of Animator that take a list of values. But an Animator can - * be constructed without values and this method can be called to set the values manually - * instead. - * - * @param values The set of values, per property, being animated between. - */ - public void setValues(PropertyValuesHolder... values) { - int numValues = values.length; - mValues = values; - mValuesMap = new HashMap(numValues); - for (int i = 0; i < numValues; ++i) { - PropertyValuesHolder valuesHolder = (PropertyValuesHolder) values[i]; - mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); - } - } - - /** - * Returns the values that this Animator animates between. These values are stored in - * PropertyValuesHolder objects, even if the Animator was created with a simple list - * of value objects instead. - * - * @return PropertyValuesHolder[] An array of PropertyValuesHolder objects which hold the - * values, per property, that define the animation. - */ - public PropertyValuesHolder[] getValues() { - return mValues; - } - - /** - * Sets the values to animate between for this animation. If values is - * a set of PropertyValuesHolder objects, these objects will become the set of properties - * animated and the values that those properties are animated between. Otherwise, this method - * will set only one set of values for the Animator. Also, if the values are not - * PropertyValuesHolder objects and if there are already multiple sets of - * values defined for this Animator via - * more than one PropertyValuesHolder objects, this method will set the values for - * the first of those objects. - * - * @param values The set of values to animate between. - */ - public void setValues(T... values) { - if (mValues == null || mValues.length == 0) { - setValues(new PropertyValuesHolder[]{ - new PropertyValuesHolder("", (Object[])values)}); - } else { - PropertyValuesHolder valuesHolder = mValues[0]; - valuesHolder.setValues(values); - } - } - - /** - * This function is called immediately before processing the first animation - * frame of an animation. If there is a nonzero startDelay, the - * function is called after that delay ends. - * It takes care of the final initialization steps for the - * animation. - * - *

Overrides of this method should call the superclass method to ensure - * that internal mechanisms for the animation are set up correctly.

- */ - void initAnimation() { - if (!mInitialized) { - int numValues = mValues.length; - for (int i = 0; i < numValues; ++i) { - mValues[i].init(); - } - mCurrentIteration = 0; - mInitialized = true; - } - } - + ArrayList mListeners = null; /** - * Sets the length of the animation. - * - * @param duration The length of the animation, in milliseconds. + * Starts this animation. If the animation has a nonzero startDelay, the animation will start + * running after that delay elapses. Note that the animation does not start synchronously with + * 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. */ - public void setDuration(long duration) { - mDuration = duration; - } - - /** - * Gets the length of the animation. - * - * @return The length of the animation, in milliseconds. - */ - public long getDuration() { - return mDuration; - } - - /** - * Sets the position of the animation to the specified point in time. This time should - * be between 0 and the total duration of the animation, including any repetition. If - * the animation has not yet been started, then it will not advance forward after it is - * set to this time; it will simply set the time to this value and perform any appropriate - * actions based on that time. If the animation is already running, then setCurrentPlayTime() - * will set the current playing time to this value and continue playing from that point. - * - * @param playTime The time, in milliseconds, to which the animation is advanced or rewound. - */ - public void setCurrentPlayTime(long playTime) { - initAnimation(); - long currentTime = AnimationUtils.currentAnimationTimeMillis(); - if (mPlayingState != RUNNING) { - mSeekTime = playTime; - mPlayingState = SEEKED; - } - mStartTime = currentTime - playTime; - animationFrame(currentTime); + public void start() { } /** - * Gets the current position of the animation in time, which is equal to the current - * time minus the time that the animation started. An animation that is not yet started will - * return a value of zero. - * - * @return The current position in time of the animation. + * Cancels the animation. Unlike {@link #end()}, cancel() 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. */ - public long getCurrentPlayTime() { - if (!mInitialized || mPlayingState == STOPPED) { - return 0; - } - return AnimationUtils.currentAnimationTimeMillis() - mStartTime; + public void cancel() { } /** - * This custom, static handler handles the timing pulse that is shared by - * all active animations. This approach ensures that the setting of animation - * values will happen on the UI thread and that all animations will share - * the same times for calculating their values, which makes synchronizing - * animations possible. - * + * 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 + * its listeners. */ - private static class AnimationHandler extends Handler { - /** - * There are only two messages that we care about: ANIMATION_START and - * ANIMATION_FRAME. The START message is sent when an animation's start() - * method is called. It cannot start synchronously when start() is called - * because the call may be on the wrong thread, and it would also not be - * synchronized with other animations because it would not start on a common - * timing pulse. So each animation sends a START message to the handler, which - * causes the handler to place the animation on the active animations queue and - * start processing frames for that animation. - * The FRAME message is the one that is sent over and over while there are any - * active animations to process. - */ - @Override - public void handleMessage(Message msg) { - boolean callAgain = true; - switch (msg.what) { - // TODO: should we avoid sending frame message when starting if we - // were already running? - case ANIMATION_START: - if (sAnimations.size() > 0 || sDelayedAnims.size() > 0) { - callAgain = false; - } - // pendingAnims holds any animations that have requested to be started - // We're going to clear sPendingAnimations, but starting animation may - // cause more to be added to the pending list (for example, if one animation - // starting triggers another starting). So we loop until sPendingAnimations - // is empty. - while (sPendingAnimations.size() > 0) { - ArrayList pendingCopy = - (ArrayList) sPendingAnimations.clone(); - sPendingAnimations.clear(); - int count = pendingCopy.size(); - for (int i = 0; i < count; ++i) { - Animator 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) { - anim.startAnimation(); - } else { - sDelayedAnims.add(anim); - } - } - } - // fall through to process first frame of new animations - case ANIMATION_FRAME: - // currentTime holds the common time for all animations processed - // during this frame - long currentTime = AnimationUtils.currentAnimationTimeMillis(); - - // First, process animations currently sitting on the delayed queue, adding - // them to the active animations if they are ready - int numDelayedAnims = sDelayedAnims.size(); - for (int i = 0; i < numDelayedAnims; ++i) { - Animator anim = sDelayedAnims.get(i); - if (anim.delayedAnimationFrame(currentTime)) { - sReadyAnims.add(anim); - } - } - int numReadyAnims = sReadyAnims.size(); - if (numReadyAnims > 0) { - for (int i = 0; i < numReadyAnims; ++i) { - Animator anim = sReadyAnims.get(i); - anim.startAnimation(); - sDelayedAnims.remove(anim); - } - sReadyAnims.clear(); - } - - // Now process all active animations. The return value from animationFrame() - // tells the handler whether it should now be ended - int numAnims = sAnimations.size(); - for (int i = 0; i < numAnims; ++i) { - Animator anim = sAnimations.get(i); - if (anim.animationFrame(currentTime)) { - sEndingAnims.add(anim); - } - } - if (sEndingAnims.size() > 0) { - for (int i = 0; i < sEndingAnims.size(); ++i) { - sEndingAnims.get(i).endAnimation(); - } - sEndingAnims.clear(); - } - - // If there are still active or delayed animations, call the handler again - // after the frameDelay - if (callAgain && (!sAnimations.isEmpty() || !sDelayedAnims.isEmpty())) { - sendEmptyMessageDelayed(ANIMATION_FRAME, sFrameDelay); - } - break; - } - } + public void end() { } /** @@ -479,9 +64,7 @@ public class Animator extends Animatable { * * @return the number of milliseconds to delay running the animation */ - public long getStartDelay() { - return mStartDelay; - } + public abstract long getStartDelay(); /** * The amount of time, in milliseconds, to delay starting the animation after @@ -489,485 +72,177 @@ public class Animator extends Animatable { * @param startDelay The amount of the delay, in milliseconds */ - public void setStartDelay(long startDelay) { - this.mStartDelay = startDelay; - } + public abstract void setStartDelay(long startDelay); - /** - * The amount of time, in milliseconds, between each frame of the animation. This is a - * requested time that the animation will attempt to honor, but the actual delay between - * frames may be different, depending on system load and capabilities. This is a static - * function because the same delay will be applied to all animations, since they are all - * run off of a single timing loop. - * - * @return the requested time between frames, in milliseconds - */ - public static long getFrameDelay() { - return sFrameDelay; - } - - /** - * The amount of time, in milliseconds, between each frame of the animation. This is a - * requested time that the animation will attempt to honor, but the actual delay between - * frames may be different, depending on system load and capabilities. This is a static - * function because the same delay will be applied to all animations, since they are all - * run off of a single timing loop. - * - * @param frameDelay the requested time between frames, in milliseconds - */ - public static void setFrameDelay(long frameDelay) { - sFrameDelay = frameDelay; - } /** - * The most recent value calculated by this Animator when there is just one - * property being animated. This value is only sensible while the animation is running. The main - * purpose for this read-only property is to retrieve the value from the Animator - * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(Animator)}, which - * is called during each animation frame, immediately after the value is calculated. - * - * @return animatedValue The value most recently calculated by this Animator for - * the single property being animated. If there are several properties being animated - * (specified by several PropertyValuesHolder objects in the constructor), this function - * returns the animated value for the first of those objects. - */ - public Object getAnimatedValue() { - if (mValues != null && mValues.length > 0) { - return mValues[0].getAnimatedValue(); - } - // Shouldn't get here; should always have values unless Animator was set up wrong - return null; - } - - /** - * The most recent value calculated by this Animator for propertyName. - * The main purpose for this read-only property is to retrieve the value from the - * Animator during a call to - * {@link AnimatorUpdateListener#onAnimationUpdate(Animator)}, which - * is called during each animation frame, immediately after the value is calculated. + * Sets the length of the animation. * - * @return animatedValue The value most recently calculated for the named property - * by this Animator. + * @param duration The length of the animation, in milliseconds. */ - public Object getAnimatedValue(String propertyName) { - PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName); - if (valuesHolder != null) { - return valuesHolder.getAnimatedValue(); - } else { - // At least avoid crashing if called with bogus propertyName - return null; - } - } + public abstract void setDuration(long duration); /** - * Sets how many times the animation should be repeated. If the repeat - * count is 0, the animation is never repeated. If the repeat count is - * greater than 0 or {@link #INFINITE}, the repeat mode will be taken - * into account. The repeat count is 0 by default. - * - * @param value the number of times the animation should be repeated - */ - public void setRepeatCount(int value) { - mRepeatCount = value; - } - /** - * Defines how many times the animation should repeat. The default value - * is 0. + * Gets the length of the animation. * - * @return the number of times the animation should repeat, or {@link #INFINITE} + * @return The length of the animation, in milliseconds. */ - public int getRepeatCount() { - return mRepeatCount; - } + public abstract long getDuration(); /** - * Defines what this animation should do when it reaches the end. This - * setting is applied only when the repeat count is either greater than - * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}. + * The time interpolator used in calculating the elapsed fraction of this animation. The + * interpolator determines whether the animation runs with linear or non-linear motion, + * such as acceleration and deceleration. The default value is + * {@link android.view.animation.AccelerateDecelerateInterpolator} * - * @param value {@link #RESTART} or {@link #REVERSE} + * @param value the interpolator to be used by this animation */ - public void setRepeatMode(int value) { - mRepeatMode = value; - } + public abstract void setInterpolator(Interpolator value); /** - * Defines what this animation should do when it reaches the end. - * - * @return either one of {@link #REVERSE} or {@link #RESTART} + * Returns whether this Animator is currently running (having been started and not yet ended). + * @return Whether the Animator is running. */ - public int getRepeatMode() { - return mRepeatMode; - } + public abstract boolean isRunning(); /** - * Adds a listener to the set of listeners that are sent update events through the life of - * an animation. This method is called on all listeners for every frame of the animation, - * after the values for the animation have been calculated. + * Adds a listener to the set of listeners that are sent events through the life of an + * animation, such as start, repeat, and end. * * @param listener the listener to be added to the current set of listeners for this animation. */ - public void addUpdateListener(AnimatorUpdateListener listener) { - if (mUpdateListeners == null) { - mUpdateListeners = new ArrayList(); + public void addListener(AnimatorListener listener) { + if (mListeners == null) { + mListeners = new ArrayList(); } - mUpdateListeners.add(listener); + mListeners.add(listener); } /** - * Removes a listener from the set listening to frame updates for this animation. + * Removes a listener from the set listening to this animation. * - * @param listener the listener to be removed from the current set of update listeners - * for this animation. + * @param listener the listener to be removed from the current set of listeners for this + * animation. */ - public void removeUpdateListener(AnimatorUpdateListener listener) { - if (mUpdateListeners == null) { + public void removeListener(AnimatorListener listener) { + if (mListeners == null) { return; } - mUpdateListeners.remove(listener); - if (mUpdateListeners.size() == 0) { - mUpdateListeners = null; + mListeners.remove(listener); + if (mListeners.size() == 0) { + mListeners = null; } } - /** - * The time interpolator used in calculating the elapsed fraction of this animation. The - * interpolator determines whether the animation runs with linear or non-linear motion, - * such as acceleration and deceleration. The default value is - * {@link android.view.animation.AccelerateDecelerateInterpolator} + * Gets the set of {@link android.animation.Animator.AnimatorListener} objects that are currently + * listening for events on this Animator object. * - * @param value the interpolator to be used by this animation + * @return ArrayList The set of listeners. */ - @Override - public void setInterpolator(Interpolator value) { - if (value != null) { - mInterpolator = value; - } + public ArrayList getListeners() { + return mListeners; } /** - * Returns the timing interpolator that this Animator uses. - * - * @return The timing interpolator for this Animator. + * Removes all listeners from this object. This is equivalent to calling + * getListeners() followed by calling clear() on the + * returned list of listeners. */ - public Interpolator getInterpolator() { - return mInterpolator; - } - - /** - * The type evaluator to be used when calculating the animated values of this animation. - * The system will automatically assign a float, int, or double evaluator based on the type - * of startValue and endValue in the constructor. But if these values - * are not one of these primitive types, or if different evaluation is desired (such as is - * necessary with int values that represent colors), a custom evaluator needs to be assigned. - * For example, when running an animation on color values, the {@link RGBEvaluator} - * should be used to get correct RGB color interpolation. - * - *

If this Animator has only one set of values being animated between, this evaluator - * will be used for that set. If there are several sets of values being animated, which is - * the case if PropertyValuesHOlder objects were set on the Animator, then the evaluator - * is assigned just to the first PropertyValuesHolder object.

- * - * @param value the evaluator to be used this animation - */ - public void setEvaluator(TypeEvaluator value) { - if (value != null && mValues != null && mValues.length > 0) { - mValues[0].setEvaluator(value); - } - } - - /** - * Start the animation playing. This version of start() takes a boolean flag that indicates - * whether the animation should play in reverse. The flag is usually false, but may be set - * to true if called from the reverse() method/ - * - * @param playBackwards Whether the Animator should start playing in reverse. - */ - private void start(boolean playBackwards) { - mPlayingBackwards = playBackwards; - if ((mStartDelay == 0) && (Thread.currentThread() == Looper.getMainLooper().getThread())) { - // This sets the initial value of the animation, prior to actually starting it running - setCurrentPlayTime(getCurrentPlayTime()); - } - mPlayingState = STOPPED; - mStartedDelay = false; - sPendingAnimations.add(this); - if (sAnimationHandler == null) { - sAnimationHandler = new AnimationHandler(); - } - // TODO: does this put too many messages on the queue if the handler - // is already running? - sAnimationHandler.sendEmptyMessage(ANIMATION_START); - } - - @Override - public void start() { - start(false); - } - - @Override - public void cancel() { + public void removeAllListeners() { if (mListeners != null) { - ArrayList tmpListeners = - (ArrayList) mListeners.clone(); - for (AnimatableListener listener : tmpListeners) { - listener.onAnimationCancel(this); - } + mListeners.clear(); + mListeners = null; } - // Just set the CANCELED flag - this causes the animation to end the next time a frame - // is processed. - mPlayingState = CANCELED; } @Override - public void end() { - if (!sAnimations.contains(this) && !sPendingAnimations.contains(this)) { - // Special case if the animation has not yet started; get it ready for ending - mStartedDelay = false; - sPendingAnimations.add(this); - if (sAnimationHandler == null) { - sAnimationHandler = new AnimationHandler(); + public Animator clone() { + try { + final Animator anim = (Animator) super.clone(); + if (mListeners != null) { + ArrayList oldListeners = mListeners; + anim.mListeners = new ArrayList(); + int numListeners = oldListeners.size(); + for (int i = 0; i < numListeners; ++i) { + anim.mListeners.add(oldListeners.get(i)); + } } - sAnimationHandler.sendEmptyMessage(ANIMATION_START); + return anim; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); } - // Just set the ENDED flag - this causes the animation to end the next time a frame - // is processed. - mPlayingState = ENDED; - } - - @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); } /** - * Plays the Animator in reverse. If the animation is already running, - * it will stop itself and play backwards from the point reached when reverse was called. - * If the animation is not currently running, then it will start from the end and - * play backwards. This behavior is only set for the current animation; future playing - * of the animation will use the default behavior of playing forward. + * This method tells the object to use appropriate information to extract + * starting values for the animation. For example, a AnimatorSet object will pass + * this call to its child objects to tell them to set up the values. A + * ObjectAnimator object will use the information it has about its target object + * and PropertyValuesHolder objects to get the start values for its properties. + * An ValueAnimator object will ignore the request since it does not have enough + * information (such as a target object) to gather these values. */ - public void reverse() { - mPlayingBackwards = !mPlayingBackwards; - if (mPlayingState == RUNNING) { - long currentTime = AnimationUtils.currentAnimationTimeMillis(); - long currentPlayTime = currentTime - mStartTime; - long timeLeft = mDuration - currentPlayTime; - mStartTime = currentTime - timeLeft; - } else { - start(true); - } + public void setupStartValues() { } /** - * Called internally to end an animation by removing it from the animations list. Must be - * called on the UI thread. + * This method tells the object to use appropriate information to extract + * ending values for the animation. For example, a AnimatorSet object will pass + * this call to its child objects to tell them to set up the values. A + * ObjectAnimator object will use the information it has about its target object + * and PropertyValuesHolder objects to get the start values for its properties. + * An ValueAnimator object will ignore the request since it does not have enough + * information (such as a target object) to gather these values. */ - private void endAnimation() { - sAnimations.remove(this); - mPlayingState = STOPPED; - if (mListeners != null) { - ArrayList tmpListeners = - (ArrayList) mListeners.clone(); - for (AnimatableListener listener : tmpListeners) { - listener.onAnimationEnd(this); - } - } + public void setupEndValues() { } /** - * Called internally to start an animation by adding it to the active animations list. Must be - * called on the UI thread. - */ - private void startAnimation() { - initAnimation(); - sAnimations.add(this); - if (mListeners != null) { - ArrayList tmpListeners = - (ArrayList) mListeners.clone(); - for (AnimatableListener listener : tmpListeners) { - listener.onAnimationStart(this); - } - } - } - - /** - * Internal function called to process an animation frame on an animation that is currently - * sleeping through its startDelay phase. The return value indicates whether it - * should be woken up and put on the active animations queue. + * Sets the target object whose property will be animated by this animation. Not all subclasses + * operate on target objects (for example, {@link ValueAnimator}, but this method + * is on the superclass for the convenience of dealing generically with those subclasses + * that do handle targets. * - * @param currentTime The current animation time, used to calculate whether the animation - * has exceeded its startDelay and should be started. - * @return True if the animation's startDelay has been exceeded and the animation - * should be added to the set of active animations. + * @param target The object being animated */ - 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; - } else { - long deltaTime = currentTime - mDelayStartTime; - if (deltaTime > mStartDelay) { - // startDelay ended - start the anim and record the - // mStartTime appropriately - mStartTime = currentTime - (deltaTime - mStartDelay); - mPlayingState = RUNNING; - return true; - } - } - return false; + public void setTarget(Object target) { } /** - * This internal function processes a single animation frame for a given animation. The - * currentTime parameter is the timing pulse sent by the handler, used to calculate the - * elapsed duration, and therefore - * the elapsed fraction, of the animation. The return value indicates whether the animation - * should be ended (which happens when the elapsed time of the animation exceeds the - * animation's duration, including the repeatCount). - * - * @param currentTime The current time, as tracked by the static timing handler - * @return true if the animation's duration, including any repetitions due to - * repeatCount has been exceeded and the animation should be ended. + *

An animation listener receives notifications from an animation. + * Notifications indicate animation related events, such as the end or the + * repetition of the animation.

*/ - private boolean animationFrame(long currentTime) { - boolean done = false; - - if (mPlayingState == STOPPED) { - mPlayingState = RUNNING; - if (mSeekTime < 0) { - mStartTime = currentTime; - } else { - mStartTime = currentTime - mSeekTime; - // Now that we're playing, reset the seek time - mSeekTime = -1; - } - } - switch (mPlayingState) { - case RUNNING: - case SEEKED: - float fraction = (float)(currentTime - mStartTime) / mDuration; - if (fraction >= 1f) { - if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) { - // Time to repeat - if (mListeners != null) { - for (AnimatableListener listener : mListeners) { - listener.onAnimationRepeat(this); - } - } - ++mCurrentIteration; - if (mRepeatMode == REVERSE) { - mPlayingBackwards = mPlayingBackwards ? false : true; - } - // TODO: doesn't account for fraction going Wayyyyy over 1, like 2+ - fraction = fraction - 1f; - mStartTime += mDuration; - } else { - done = true; - fraction = Math.min(fraction, 1.0f); - } - } - if (mPlayingBackwards) { - fraction = 1f - fraction; - } - 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; - } + public static interface AnimatorListener { + /** + *

Notifies the start of the animation.

+ * + * @param animation The started animation. + */ + void onAnimationStart(Animator animation); - /** - * This method is called with the elapsed fraction of the animation during every - * animation frame. This function turns the elapsed fraction into an interpolated fraction - * and then into an animated value (from the evaluator. The function is called mostly during - * animation updates, but it is also called when the end() - * function is called, to set the final value on the property. - * - *

Overrides of this method must call the superclass to perform the calculation - * of the animated value.

- * - * @param fraction The elapsed fraction of the animation. - */ - void animateValue(float fraction) { - fraction = mInterpolator.getInterpolation(fraction); - int numValues = mValues.length; - 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); - } - } - } + /** + *

Notifies the end of the animation. This callback is not invoked + * for animations with repeat count set to INFINITE.

+ * + * @param animation The animation which reached its end. + */ + void onAnimationEnd(Animator animation); - @Override - public Animator clone() { - final Animator anim = (Animator) super.clone(); - if (mUpdateListeners != null) { - ArrayList oldListeners = mUpdateListeners; - anim.mUpdateListeners = new ArrayList(); - int numListeners = oldListeners.size(); - for (int i = 0; i < numListeners; ++i) { - anim.mUpdateListeners.add(oldListeners.get(i)); - } - } - anim.mSeekTime = -1; - anim.mPlayingBackwards = false; - anim.mCurrentIteration = 0; - anim.mInitialized = false; - anim.mPlayingState = STOPPED; - anim.mStartedDelay = false; - PropertyValuesHolder[] oldValues = mValues; - if (oldValues != null) { - int numValues = oldValues.length; - anim.mValues = new PropertyValuesHolder[numValues]; - for (int i = 0; i < numValues; ++i) { - anim.mValues[i] = oldValues[i].clone(); - } - anim.mValuesMap = new HashMap(numValues); - for (int i = 0; i < numValues; ++i) { - PropertyValuesHolder valuesHolder = mValues[i]; - anim.mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); - } - } - return anim; - } + /** + *

Notifies the cancellation of the animation. This callback is not invoked + * for animations with repeat count set to INFINITE.

+ * + * @param animation The animation which was canceled. + */ + void onAnimationCancel(Animator animation); - /** - * Implementors of this interface can add themselves as update listeners - * to an Animator instance to receive callbacks on every animation - * frame, after the current frame's values have been calculated for that - * Animator. - */ - public static interface AnimatorUpdateListener { /** - *

Notifies the occurrence of another frame of the animation.

+ *

Notifies the repetition of the animation.

* * @param animation The animation which was repeated. */ - void onAnimationUpdate(Animator animation); - + void onAnimationRepeat(Animator animation); } -} \ No newline at end of file +} diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java new file mode 100644 index 0000000..0016459 --- /dev/null +++ b/core/java/android/animation/AnimatorInflater.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2010 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 android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.content.res.Resources.NotFoundException; +import android.util.AttributeSet; +import android.util.Xml; +import android.view.animation.AnimationUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class is used to instantiate menu XML files into Animator objects. + *

+ * For performance reasons, menu inflation relies heavily on pre-processing of + * XML files that is done at build time. Therefore, it is not currently possible + * to use MenuInflater with an XmlPullParser over a plain XML file at runtime; + * it only works with an XmlPullParser returned from a compiled resource (R. + * something file.) + */ +public class AnimatorInflater { + + /** + * These flags are used when parsing AnimatorSet objects + */ + private static final int TOGETHER = 0; + private static final int SEQUENTIALLY = 1; + + /** + * Enum values used in XML attributes to indicate the value for mValueType + */ + private static final int VALUE_TYPE_FLOAT = 0; + private static final int VALUE_TYPE_INT = 1; + private static final int VALUE_TYPE_DOUBLE = 2; + private static final int VALUE_TYPE_COLOR = 3; + private static final int VALUE_TYPE_CUSTOM = 4; + + /** + * Loads an {@link Animator} object from a resource + * + * @param context Application context used to access resources + * @param id The resource id of the animation to load + * @return The animator object reference by the specified id + * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded + */ + public static Animator loadAnimator(Context context, int id) + throws NotFoundException { + + XmlResourceParser parser = null; + try { + parser = context.getResources().getAnimation(id); + return createAnimatorFromXml(context, parser); + } catch (XmlPullParserException ex) { + Resources.NotFoundException rnf = + new Resources.NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } catch (IOException ex) { + Resources.NotFoundException rnf = + new Resources.NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } finally { + if (parser != null) parser.close(); + } + } + + private static Animator createAnimatorFromXml(Context c, XmlPullParser parser) + throws XmlPullParserException, IOException { + + return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0); + } + + private static Animator createAnimatorFromXml(Context c, XmlPullParser parser, + AttributeSet attrs, AnimatorSet parent, int sequenceOrdering) + throws XmlPullParserException, IOException { + + Animator anim = null; + ArrayList childAnims = null; + + // Make sure we are on a start tag. + int type; + int depth = parser.getDepth(); + + while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + + if (name.equals("objectAnimator")) { + anim = loadObjectAnimator(c, attrs); + } else if (name.equals("animator")) { + anim = loadAnimator(c, attrs, null); + } else if (name.equals("set")) { + anim = new AnimatorSet(); + TypedArray a = c.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.AnimatorSet); + int ordering = a.getInt(com.android.internal.R.styleable.AnimatorSet_ordering, + TOGETHER); + createAnimatorFromXml(c, parser, attrs, (AnimatorSet) anim, ordering); + a.recycle(); + } else { + throw new RuntimeException("Unknown animator name: " + parser.getName()); + } + + if (parent != null) { + if (childAnims == null) { + childAnims = new ArrayList(); + } + childAnims.add(anim); + } + } + if (parent != null && childAnims != null) { + Animator[] animsArray = new Animator[childAnims.size()]; + int index = 0; + for (Animator a : childAnims) { + animsArray[index++] = a; + } + if (sequenceOrdering == TOGETHER) { + parent.playTogether(animsArray); + } else { + parent.playSequentially(animsArray); + } + } + + return anim; + + } + + private static ObjectAnimator loadObjectAnimator(Context context, AttributeSet attrs) + throws NotFoundException { + + ObjectAnimator anim = new ObjectAnimator(); + + loadAnimator(context, attrs, anim); + + TypedArray a = + context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.PropertyAnimator); + + String propertyName = a.getString(com.android.internal.R.styleable.PropertyAnimator_propertyName); + + anim.setPropertyName(propertyName); + + a.recycle(); + + return anim; + } + + /** + * Creates a new animation whose parameters come from the specified context and + * attributes set. + * + * @param context the application environment + * @param attrs the set of attributes holding the animation parameters + */ + private static ValueAnimator loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim) + throws NotFoundException { + + TypedArray a = + context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator); + + long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 0); + + long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0); + + int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType, + VALUE_TYPE_FLOAT); + + Object valueFrom = null; + Object valueTo = null; + TypeEvaluator evaluator = null; + + switch (valueType) { + case VALUE_TYPE_FLOAT: + if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) { + valueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f); + } + if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) { + valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f); + } + break; + case VALUE_TYPE_COLOR: + evaluator = new RGBEvaluator(); + // fall through to pick up values + case VALUE_TYPE_INT: + if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) { + valueFrom = a.getInteger(com.android.internal.R.styleable.Animator_valueFrom, 0); + } + if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) { + valueTo = a.getInteger(com.android.internal.R.styleable.Animator_valueTo, 0); + } + break; + case VALUE_TYPE_DOUBLE: + if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) { + valueFrom = (Double)((Float)(a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f))).doubleValue(); + } + if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) { + valueTo = (Double)((Float)a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f)).doubleValue(); + } + break; + case VALUE_TYPE_CUSTOM: + // TODO: How to get an 'Object' value? + if (a.hasValue(com.android.internal.R.styleable.Animator_valueFrom)) { + valueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f); + } + if (a.hasValue(com.android.internal.R.styleable.Animator_valueTo)) { + valueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f); + } + break; + } + + if (anim == null) { + anim = new ValueAnimator(duration, valueFrom, valueTo); + } else { + anim.setDuration(duration); + anim.setValues(valueFrom, valueTo); + } + + anim.setStartDelay(startDelay); + + if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) { + anim.setRepeatCount( + a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0)); + } + if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) { + anim.setRepeatMode( + a.getInt(com.android.internal.R.styleable.Animator_repeatMode, + ValueAnimator.RESTART)); + } + if (evaluator != null) { + anim.setEvaluator(evaluator); + } + + final int resID = + a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0); + if (resID > 0) { + anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID)); + } + a.recycle(); + + return anim; + } +} diff --git a/core/java/android/animation/AnimatorListenerAdapter.java b/core/java/android/animation/AnimatorListenerAdapter.java new file mode 100644 index 0000000..6182389 --- /dev/null +++ b/core/java/android/animation/AnimatorListenerAdapter.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 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; + +/** + * This adapter class provides empty implementations of the methods from {@link android.animation.Animator.AnimatorListener}. + * Any custom listener that cares only about a subset of the methods of this listener can + * simply subclass this adapter class instead of implementing the interface directly. + */ +public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener { + + /** + * {@inheritdoc} + */ + @Override + public void onAnimationCancel(Animator animation) { + } + + /** + * {@inheritdoc} + */ + @Override + public void onAnimationEnd(Animator animation) { + } + + /** + * {@inheritdoc} + */ + @Override + public void onAnimationRepeat(Animator animation) { + } + + /** + * {@inheritdoc} + */ + @Override + public void onAnimationStart(Animator animation) { + } + +} diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java new file mode 100644 index 0000000..a8385e4 --- /dev/null +++ b/core/java/android/animation/AnimatorSet.java @@ -0,0 +1,939 @@ +/* + * Copyright (C) 2010 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 android.view.animation.Interpolator; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * This class plays a set of {@link Animator} objects in the specified order. Animations + * can be set up to play together, in sequence, or after a specified delay. + * + *

There are two different approaches to adding animations to a AnimatorSet: + * either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or + * {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add + * a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be + * used in conjunction with methods in the {@link AnimatorSet.Builder Builder} + * class to add animations + * one by one.

+ * + *

It is possible to set up a AnimatorSet with circular dependencies between + * its animations. For example, an animation a1 could be set up to start before animation a2, a2 + * before a3, and a3 before a1. The results of this configuration are undefined, but will typically + * result in none of the affected animations being played. Because of this (and because + * circular dependencies do not make logical sense anyway), circular dependencies + * should be avoided, and the dependency flow of animations should only be in one direction. + */ +public final class AnimatorSet extends Animator { + + /** + * Internal variables + * NOTE: This object implements the clone() method, making a deep copy of any referenced + * objects. As other non-trivial fields are added to this class, make sure to add logic + * to clone() to make deep copies of them. + */ + + /** + * Tracks animations currently being played, so that we know what to + * cancel or end when cancel() or end() is called on this AnimatorSet + */ + private ArrayList mPlayingSet = new ArrayList(); + + /** + * Contains all nodes, mapped to their respective Animators. When new + * dependency information is added for an Animator, we want to add it + * to a single node representing that Animator, not create a new Node + * if one already exists. + */ + private HashMap mNodeMap = new HashMap(); + + /** + * Set of all nodes created for this AnimatorSet. This list is used upon + * starting the set, and the nodes are placed in sorted order into the + * sortedNodes collection. + */ + private ArrayList mNodes = new ArrayList(); + + /** + * The sorted list of nodes. This is the order in which the animations will + * be played. The details about when exactly they will be played depend + * on the dependency relationships of the nodes. + */ + private ArrayList mSortedNodes = new ArrayList(); + + /** + * Flag indicating whether the nodes should be sorted prior to playing. This + * flag allows us to cache the previous sorted nodes so that if the sequence + * is replayed with no changes, it does not have to re-sort the nodes again. + */ + private boolean mNeedsSort = true; + + private AnimatorSetListener mSetListener = null; + + /** + * Flag indicating that the AnimatorSet has been canceled (by calling cancel() or end()). + * This flag is used to avoid starting other animations when currently-playing + * child animations of this AnimatorSet end. + */ + boolean mCanceled = false; + + // The amount of time in ms to delay starting the animation after start() is called + private long mStartDelay = 0; + + + // 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 + // set, it is passed along to the child animations. + private long mDuration = -1; + + + /** + * Sets up this AnimatorSet to play all of the supplied animations at the same time. + * + * @param items The animations that will be started simultaneously. + */ + public void playTogether(Animator... items) { + if (items != null) { + mNeedsSort = true; + Builder builder = play(items[0]); + for (int i = 1; i < items.length; ++i) { + builder.with(items[i]); + } + } + } + + /** + * Sets up this AnimatorSet to play each of the supplied animations when the + * previous animation ends. + * + * @param items The aniamtions that will be started one after another. + */ + public void playSequentially(Animator... items) { + if (items != null) { + mNeedsSort = true; + if (items.length == 1) { + play(items[0]); + } else { + for (int i = 0; i < items.length - 1; ++i) { + play(items[i]).before(items[i+1]); + } + } + } + } + + /** + * Returns the current list of child Animator objects controlled by this + * AnimatorSet. This is a copy of the internal list; modifications to the returned list + * will not affect the AnimatorSet, although changes to the underlying Animator objects + * will affect those objects being managed by the AnimatorSet. + * + * @return ArrayList The list of child animations of this AnimatorSet. + */ + public ArrayList getChildAnimations() { + ArrayList childList = new ArrayList(); + for (Node node : mNodes) { + childList.add(node.animation); + } + return childList; + } + + /** + * Sets the target object for all current {@link #getChildAnimations() child animations} + * of this AnimatorSet that take targets ({@link ObjectAnimator} and + * AnimatorSet). + * + * @param target The object being animated + */ + @Override + public void setTarget(Object target) { + for (Node node : mNodes) { + Animator animation = node.animation; + if (animation instanceof AnimatorSet) { + ((AnimatorSet)animation).setTarget(target); + } else if (animation instanceof ObjectAnimator) { + ((ObjectAnimator)animation).setTarget(target); + } + } + } + + /** + * Sets the Interpolator for all current {@link #getChildAnimations() child animations} + * of this AnimatorSet. + * + * @param interpolator the interpolator to be used by each child animation of this AnimatorSet + */ + @Override + public void setInterpolator(Interpolator interpolator) { + for (Node node : mNodes) { + node.animation.setInterpolator(interpolator); + } + } + + /** + * This method creates a Builder object, which is used to + * set up playing constraints. This initial play() method + * tells the Builder the animation that is the dependency for + * the succeeding commands to the Builder. For example, + * calling play(a1).with(a2) sets up the AnimatorSet to play + * a1 and a2 at the same time, + * play(a1).before(a2) sets up the AnimatorSet to play + * a1 first, followed by a2, and + * play(a1).after(a2) sets up the AnimatorSet to play + * a2 first, followed by a1. + * + *

Note that play() is the only way to tell the + * Builder the animation upon which the dependency is created, + * so successive calls to the various functions in Builder + * will all refer to the initial parameter supplied in play() + * as the dependency of the other animations. For example, calling + * play(a1).before(a2).before(a3) will play both a2 + * and a3 when a1 ends; it does not set up a dependency between + * a2 and a3.

+ * + * @param anim The animation that is the dependency used in later calls to the + * methods in the returned Builder object. A null parameter will result + * in a null Builder return value. + * @return Builder The object that constructs the AnimatorSet based on the dependencies + * outlined in the calls to play and the other methods in the + * BuilderNote that canceling a AnimatorSet also cancels all of the animations that it is + * responsible for.

+ */ + @SuppressWarnings("unchecked") + @Override + public void cancel() { + mCanceled = true; + if (mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationCancel(this); + } + } + if (mSortedNodes.size() > 0) { + for (Node node : mSortedNodes) { + node.animation.cancel(); + } + } + } + + /** + * {@inheritDoc} + * + *

Note that ending a AnimatorSet also ends all of the animations that it is + * responsible for.

+ */ + @Override + public void end() { + mCanceled = true; + if (mSortedNodes.size() != mNodes.size()) { + // hasn't been started yet - sort the nodes now, then end them + sortNodes(); + for (Node node : mSortedNodes) { + if (mSetListener == null) { + mSetListener = new AnimatorSetListener(this); + } + node.animation.addListener(mSetListener); + } + } + if (mSortedNodes.size() > 0) { + for (Node node : mSortedNodes) { + node.animation.end(); + } + } + } + + /** + * Returns true if any of the child animations of this AnimatorSet have been started and have not + * yet ended. + * @return Whether this AnimatorSet has been started and has not yet ended. + */ + @Override + public boolean isRunning() { + for (Node node : mNodes) { + if (node.animation.isRunning()) { + return true; + } + } + return false; + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + * + * @return the number of milliseconds to delay running the animation + */ + @Override + public long getStartDelay() { + return mStartDelay; + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + + * @param startDelay The amount of the delay, in milliseconds + */ + @Override + public void setStartDelay(long startDelay) { + mStartDelay = startDelay; + } + + /** + * Gets the length of each of the child animations of this AnimatorSet. This value may + * be less than 0, which indicates that no duration has been set on this AnimatorSet + * and each of the child animations will use their own duration. + * + * @return The length of the animation, in milliseconds, of each of the child + * animations of this AnimatorSet. + */ + @Override + public long getDuration() { + return mDuration; + } + + /** + * Sets the length of each of the current child animations of this AnimatorSet. By default, + * each child animation will use its own duration. If the duration is set on the AnimatorSet, + * then each child animation inherits this duration. + * + * @param duration The length of the animation, in milliseconds, of each of the child + * animations of this AnimatorSet. + */ + @Override + public void setDuration(long duration) { + if (duration < 0) { + throw new IllegalArgumentException("duration must be a value of zero or greater"); + } + for (Node node : mNodes) { + // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to + // insert "play-after" delays + node.animation.setDuration(duration); + } + mDuration = duration; + } + + /** + * {@inheritDoc} + * + *

Starting this AnimatorSet will, in turn, start the animations for which + * it is responsible. The details of when exactly those animations are started depends on + * the dependency relationships that have been set up between the animations. + */ + @SuppressWarnings("unchecked") + @Override + public void start() { + mCanceled = false; + + // First, sort the nodes (if necessary). This will ensure that sortedNodes + // contains the animation nodes in the correct order. + sortNodes(); + + // 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 nodesToStart = new ArrayList(); + for (Node node : mSortedNodes) { + if (mSetListener == null) { + mSetListener = new AnimatorSetListener(this); + } + if (node.dependencies == null || node.dependencies.size() == 0) { + nodesToStart.add(node); + } else { + for (Dependency dependency : node.dependencies) { + dependency.node.animation.addListener( + new DependencyListener(this, node, dependency.rule)); + } + node.tmpDependencies = (ArrayList) node.dependencies.clone(); + } + node.animation.addListener(mSetListener); + } + // Now that all dependencies are set up, start the animations that should be started. + if (mStartDelay <= 0) { + for (Node node : nodesToStart) { + node.animation.start(); + mPlayingSet.add(node.animation); + } + } else { + // TODO: Need to cancel out of the delay appropriately + ValueAnimator delayAnim = new ValueAnimator(mStartDelay, 0f, 1f); + delayAnim.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator anim) { + for (Node node : nodesToStart) { + node.animation.start(); + mPlayingSet.add(node.animation); + } + } + }); + } + if (mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationStart(this); + } + } + } + + @Override + public AnimatorSet clone() { + final AnimatorSet anim = (AnimatorSet) super.clone(); + /* + * The basic clone() operation copies all items. This doesn't work very well for + * AnimatorSet, because it will copy references that need to be recreated and state + * that may not apply. What we need to do now is put the clone in an uninitialized + * state, with fresh, empty data structures. Then we will build up the nodes list + * manually, as we clone each Node (and its animation). The clone will then be sorted, + * and will populate any appropriate lists, when it is started. + */ + anim.mNeedsSort = true; + anim.mCanceled = false; + anim.mPlayingSet = new ArrayList(); + anim.mNodeMap = new HashMap(); + anim.mNodes = new ArrayList(); + anim.mSortedNodes = new ArrayList(); + + // Walk through the old nodes list, cloning each node and adding it to the new nodemap. + // One problem is that the old node dependencies point to nodes in the old AnimatorSet. + // We need to track the old/new nodes in order to reconstruct the dependencies in the clone. + HashMap nodeCloneMap = new HashMap(); // + for (Node node : mNodes) { + Node nodeClone = node.clone(); + nodeCloneMap.put(node, nodeClone); + anim.mNodes.add(nodeClone); + anim.mNodeMap.put(nodeClone.animation, nodeClone); + // Clear out the dependencies in the clone; we'll set these up manually later + nodeClone.dependencies = null; + nodeClone.tmpDependencies = null; + nodeClone.nodeDependents = null; + nodeClone.nodeDependencies = null; + // clear out any listeners that were set up by the AnimatorSet; these will + // be set up when the clone's nodes are sorted + ArrayList cloneListeners = nodeClone.animation.getListeners(); + if (cloneListeners != null) { + ArrayList listenersToRemove = null; + for (AnimatorListener listener : cloneListeners) { + if (listener instanceof AnimatorSetListener) { + if (listenersToRemove == null) { + listenersToRemove = new ArrayList(); + } + listenersToRemove.add(listener); + } + } + if (listenersToRemove != null) { + for (AnimatorListener listener : listenersToRemove) { + cloneListeners.remove(listener); + } + } + } + } + // Now that we've cloned all of the nodes, we're ready to walk through their + // dependencies, mapping the old dependencies to the new nodes + for (Node node : mNodes) { + Node nodeClone = nodeCloneMap.get(node); + if (node.dependencies != null) { + for (Dependency dependency : node.dependencies) { + Node clonedDependencyNode = nodeCloneMap.get(dependency.node); + Dependency cloneDependency = new Dependency(clonedDependencyNode, + dependency.rule); + nodeClone.addDependency(cloneDependency); + } + } + } + + return anim; + } + + /** + * This class is the mechanism by which animations are started based on events in other + * animations. If an animation has multiple dependencies on other animations, then + * all dependencies must be satisfied before the animation is started. + */ + private static class DependencyListener implements AnimatorListener { + + private AnimatorSet mAnimatorSet; + + // The node upon which the dependency is based. + private Node mNode; + + // The Dependency rule (WITH or AFTER) that the listener should wait for on + // the node + private int mRule; + + public DependencyListener(AnimatorSet animatorSet, Node node, int rule) { + this.mAnimatorSet = animatorSet; + this.mNode = node; + this.mRule = rule; + } + + /** + * Ignore cancel events for now. We may want to handle this eventually, + * to prevent follow-on animations from running when some dependency + * animation is canceled. + */ + public void onAnimationCancel(Animator animation) { + } + + /** + * An end event is received - see if this is an event we are listening for + */ + public void onAnimationEnd(Animator animation) { + if (mRule == Dependency.AFTER) { + startIfReady(animation); + } + } + + /** + * Ignore repeat events for now + */ + public void onAnimationRepeat(Animator animation) { + } + + /** + * A start event is received - see if this is an event we are listening for + */ + public void onAnimationStart(Animator animation) { + if (mRule == Dependency.WITH) { + startIfReady(animation); + } + } + + /** + * Check whether the event received is one that the node was waiting for. + * If so, mark it as complete and see whether it's time to start + * the animation. + * @param dependencyAnimation the animation that sent the event. + */ + private void startIfReady(Animator dependencyAnimation) { + if (mAnimatorSet.mCanceled) { + // if the parent AnimatorSet was canceled, then don't start any dependent anims + return; + } + Dependency dependencyToRemove = null; + for (Dependency dependency : mNode.tmpDependencies) { + if (dependency.rule == mRule && + dependency.node.animation == dependencyAnimation) { + // rule fired - remove the dependency and listener and check to + // see whether it's time to start the animation + dependencyToRemove = dependency; + dependencyAnimation.removeListener(this); + break; + } + } + mNode.tmpDependencies.remove(dependencyToRemove); + if (mNode.tmpDependencies.size() == 0) { + // all dependencies satisfied: start the animation + mNode.animation.start(); + mAnimatorSet.mPlayingSet.add(mNode.animation); + } + } + + } + + private class AnimatorSetListener implements AnimatorListener { + + private AnimatorSet mAnimatorSet; + + AnimatorSetListener(AnimatorSet animatorSet) { + mAnimatorSet = animatorSet; + } + + public void onAnimationCancel(Animator animation) { + if (mPlayingSet.size() == 0) { + if (mListeners != null) { + for (AnimatorListener listener : mListeners) { + listener.onAnimationCancel(mAnimatorSet); + } + } + } + } + + @SuppressWarnings("unchecked") + public void onAnimationEnd(Animator animation) { + animation.removeListener(this); + mPlayingSet.remove(animation); + Node animNode = mAnimatorSet.mNodeMap.get(animation); + animNode.done = true; + ArrayList sortedNodes = mAnimatorSet.mSortedNodes; + boolean allDone = true; + for (Node node : sortedNodes) { + if (!node.done) { + allDone = false; + break; + } + } + if (allDone) { + // If this was the last child animation to end, then notify listeners that this + // AnimatorSet has ended + if (mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationEnd(mAnimatorSet); + } + } + } + } + + // Nothing to do + public void onAnimationRepeat(Animator animation) { + } + + // Nothing to do + public void onAnimationStart(Animator animation) { + } + + } + + /** + * This method sorts the current set of nodes, if needed. The sort is a simple + * DependencyGraph sort, which goes like this: + * - All nodes without dependencies become 'roots' + * - while roots list is not null + * - for each root r + * - add r to sorted list + * - remove r as a dependency from any other node + * - any nodes with no dependencies are added to the roots list + */ + private void sortNodes() { + if (mNeedsSort) { + mSortedNodes.clear(); + ArrayList roots = new ArrayList(); + for (Node node : mNodes) { + if (node.dependencies == null || node.dependencies.size() == 0) { + roots.add(node); + } + } + ArrayList tmpRoots = new ArrayList(); + while (roots.size() > 0) { + for (Node root : roots) { + mSortedNodes.add(root); + if (root.nodeDependents != null) { + for (Node node : root.nodeDependents) { + node.nodeDependencies.remove(root); + if (node.nodeDependencies.size() == 0) { + tmpRoots.add(node); + } + } + } + } + roots.clear(); + roots.addAll(tmpRoots); + tmpRoots.clear(); + } + mNeedsSort = false; + if (mSortedNodes.size() != mNodes.size()) { + throw new IllegalStateException("Circular dependencies cannot exist" + + " in AnimatorSet"); + } + } else { + // Doesn't need sorting, but still need to add in the nodeDependencies list + // because these get removed as the event listeners fire and the dependencies + // are satisfied + for (Node node : mNodes) { + if (node.dependencies != null && node.dependencies.size() > 0) { + for (Dependency dependency : node.dependencies) { + if (node.nodeDependencies == null) { + node.nodeDependencies = new ArrayList(); + } + if (!node.nodeDependencies.contains(dependency.node)) { + node.nodeDependencies.add(dependency.node); + } + } + } + node.done = false; + } + } + } + + /** + * Dependency holds information about the node that some other node is + * dependent upon and the nature of that dependency. + * + */ + private static class Dependency { + static final int WITH = 0; // dependent node must start with this dependency node + static final int AFTER = 1; // dependent node must start when this dependency node finishes + + // The node that the other node with this Dependency is dependent upon + public Node node; + + // The nature of the dependency (WITH or AFTER) + public int rule; + + public Dependency(Node node, int rule) { + this.node = node; + this.rule = rule; + } + } + + /** + * A Node is an embodiment of both the Animator that it wraps as well as + * any dependencies that are associated with that Animation. This includes + * both dependencies upon other nodes (in the dependencies list) as + * well as dependencies of other nodes upon this (in the nodeDependents list). + */ + private static class Node implements Cloneable { + public Animator animation; + + /** + * These are the dependencies that this node's animation has on other + * nodes. For example, if this node's animation should begin with some + * other animation ends, then there will be an item in this node's + * dependencies list for that other animation's node. + */ + public ArrayList dependencies = null; + + /** + * tmpDependencies is a runtime detail. We use the dependencies list for sorting. + * But we also use the list to keep track of when multiple dependencies are satisfied, + * but removing each dependency as it is satisfied. We do not want to remove + * the dependency itself from the list, because we need to retain that information + * if the AnimatorSet is launched in the future. So we create a copy of the dependency + * list when the AnimatorSet starts and use this tmpDependencies list to track the + * list of satisfied dependencies. + */ + public ArrayList tmpDependencies = null; + + /** + * nodeDependencies is just a list of the nodes that this Node is dependent upon. + * This information is used in sortNodes(), to determine when a node is a root. + */ + public ArrayList nodeDependencies = null; + + /** + * nodeDepdendents is the list of nodes that have this node as a dependency. This + * is a utility field used in sortNodes to facilitate removing this node as a + * dependency when it is a root node. + */ + public ArrayList nodeDependents = null; + + /** + * Flag indicating whether the animation in this node is finished. This flag + * is used by AnimatorSet to check, as each animation ends, whether all child animations + * are done and it's time to send out an end event for the entire AnimatorSet. + */ + public boolean done = false; + + /** + * Constructs the Node with the animation that it encapsulates. A Node has no + * dependencies by default; dependencies are added via the addDependency() + * method. + * + * @param animation The animation that the Node encapsulates. + */ + public Node(Animator animation) { + this.animation = animation; + } + + /** + * Add a dependency to this Node. The dependency includes information about the + * node that this node is dependency upon and the nature of the dependency. + * @param dependency + */ + public void addDependency(Dependency dependency) { + if (dependencies == null) { + dependencies = new ArrayList(); + nodeDependencies = new ArrayList(); + } + dependencies.add(dependency); + if (!nodeDependencies.contains(dependency.node)) { + nodeDependencies.add(dependency.node); + } + Node dependencyNode = dependency.node; + if (dependencyNode.nodeDependents == null) { + dependencyNode.nodeDependents = new ArrayList(); + } + dependencyNode.nodeDependents.add(this); + } + + @Override + public Node clone() { + try { + Node node = (Node) super.clone(); + node.animation = (Animator) animation.clone(); + return node; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + } + + /** + * The Builder object is a utility class to facilitate adding animations to a + * AnimatorSet along with the relationships between the various animations. The + * intention of the Builder methods, along with the {@link + * AnimatorSet#play(Animator) play()} method of AnimatorSet is to make it possible to + * express the dependency relationships of animations in a natural way. Developers can also use + * the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link + * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need, + * but it might be easier in some situations to express the AnimatorSet of animations in pairs. + *

+ *

The Builder object cannot be constructed directly, but is rather constructed + * internally via a call to {@link AnimatorSet#play(Animator)}.

+ *

+ *

For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to + * play when anim2 finishes, and anim4 to play when anim3 finishes:

+ *
+     *     AnimatorSet s = new AnimatorSet();
+     *     s.play(anim1).with(anim2);
+     *     s.play(anim2).before(anim3);
+     *     s.play(anim4).after(anim3);
+     * 
+ *

+ *

Note in the example that both {@link Builder#before(Animator)} and {@link + * Builder#after(Animator)} are used. These are just different ways of expressing the same + * relationship and are provided to make it easier to say things in a way that is more natural, + * depending on the situation.

+ *

+ *

It is possible to make several calls into the same Builder object to express + * multiple relationships. However, note that it is only the animation passed into the initial + * {@link AnimatorSet#play(Animator)} method that is the dependency in any of the successive + * calls to the Builder object. For example, the following code starts both anim2 + * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and + * anim3: + *

+     *   AnimatorSet s = new AnimatorSet();
+     *   s.play(anim1).before(anim2).before(anim3);
+     * 
+ * If the desired result is to play anim1 then anim2 then anim3, this code expresses the + * relationship correctly:

+ *
+     *   AnimatorSet s = new AnimatorSet();
+     *   s.play(anim1).before(anim2);
+     *   s.play(anim2).before(anim3);
+     * 
+ *

+ *

Note that it is possible to express relationships that cannot be resolved and will not + * result in sensible results. For example, play(anim1).after(anim1) makes no + * sense. In general, circular dependencies like this one (or more indirect ones where a depends + * on b, which depends on c, which depends on a) should be avoided. Only create AnimatorSets + * that can boil down to a simple, one-way relationship of animations starting with, before, and + * after other, different, animations.

+ */ + public class Builder { + + /** + * This tracks the current node being processed. It is supplied to the play() method + * of AnimatorSet and passed into the constructor of Builder. + */ + private Node mCurrentNode; + + /** + * package-private constructor. Builders are only constructed by AnimatorSet, when the + * play() method is called. + * + * @param anim The animation that is the dependency for the other animations passed into + * the other methods of this Builder object. + */ + Builder(Animator anim) { + mCurrentNode = mNodeMap.get(anim); + if (mCurrentNode == null) { + mCurrentNode = new Node(anim); + mNodeMap.put(anim, mCurrentNode); + mNodes.add(mCurrentNode); + } + } + + /** + * Sets up the given animation to play at the same time as the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this Builder object. + * + * @param anim The animation that will play when the animation supplied to the + * {@link AnimatorSet#play(Animator)} method starts. + */ + public void with(Animator anim) { + Node node = mNodeMap.get(anim); + if (node == null) { + node = new Node(anim); + mNodeMap.put(anim, node); + mNodes.add(node); + } + Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH); + node.addDependency(dependency); + } + + /** + * Sets up the given animation to play when the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this Builder object + * ends. + * + * @param anim The animation that will play when the animation supplied to the + * {@link AnimatorSet#play(Animator)} method ends. + */ + public void before(Animator anim) { + Node node = mNodeMap.get(anim); + if (node == null) { + node = new Node(anim); + mNodeMap.put(anim, node); + mNodes.add(node); + } + Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER); + node.addDependency(dependency); + } + + /** + * Sets up the given animation to play when the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this Builder object + * to start when the animation supplied in this method call ends. + * + * @param anim The animation whose end will cause the animation supplied to the + * {@link AnimatorSet#play(Animator)} method to play. + */ + public void after(Animator anim) { + Node node = mNodeMap.get(anim); + if (node == null) { + node = new Node(anim); + mNodeMap.put(anim, node); + mNodes.add(node); + } + Dependency dependency = new Dependency(node, Dependency.AFTER); + mCurrentNode.addDependency(dependency); + } + + /** + * Sets up the animation supplied in the + * {@link AnimatorSet#play(Animator)} call that created this Builder object + * to play when the given amount of time elapses. + * + * @param delay The number of milliseconds that should elapse before the + * animation starts. + */ + public void after(long delay) { + // setup dummy ValueAnimator just to run the clock + after(new ValueAnimator(delay, 0f, 1f)); + } + + } + +} diff --git a/core/java/android/animation/Keyframe.java b/core/java/android/animation/Keyframe.java index 7d4d104..192ba5c 100644 --- a/core/java/android/animation/Keyframe.java +++ b/core/java/android/animation/Keyframe.java @@ -20,7 +20,7 @@ import android.view.animation.Interpolator; /** * This class holds a time/value pair for an animation. The Keyframe class is used - * by {@link Animator} to define the values that the animation target will have over the course + * by {@link ValueAnimator} to define the values that the animation target will have over the course * of the animation. As the time proceeds from one keyframe to the other, the value of the * target object will animate between the value at the previous keyframe and the value at the * next keyframe. Each keyframe also holds an option {@link android.view.animation.Interpolator} @@ -59,7 +59,7 @@ public class Keyframe implements Cloneable { * the time in this keyframe, and the the value animated from as the time passes the time in * this keyframe. * @param valueType The type of the value object. This is used by the - * {@link #getValue()} functionm, which is queried by {@link Animator} to determine + * {@link #getValue()} functionm, which is queried by {@link ValueAnimator} to determine * the type of {@link TypeEvaluator} to use to interpolate between values. */ private Keyframe(float fraction, Object value, Class valueType) { @@ -239,7 +239,7 @@ public class Keyframe implements Cloneable { } /** - * Gets the type of keyframe. This information is used by Animator to determine the type of + * Gets the type of keyframe. This information is used by ValueAnimator to determine the type of * {@link TypeEvaluator} to use when calculating values between keyframes. The type is based * on the type of Keyframe created. * diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java index d144b9c..af47a15 100644 --- a/core/java/android/animation/KeyframeSet.java +++ b/core/java/android/animation/KeyframeSet.java @@ -21,7 +21,7 @@ import java.util.ArrayList; import android.view.animation.Interpolator; /** - * This class holds a collection of Keyframe objects and is called by Animator to calculate + * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate * values between those keyframes for a given animation. The class internal to the animation * package because it is an implementation detail of how Keyframes are stored and used. */ diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java index 5dfdfbd..d1bc9bd 100644 --- a/core/java/android/animation/LayoutTransition.java +++ b/core/java/android/animation/LayoutTransition.java @@ -32,8 +32,8 @@ import java.util.List; * transitions for a layout container, create a LayoutTransition object and set it on any * ViewGroup by calling {@link ViewGroup#setLayoutTransition(LayoutTransition)}. This will cause * default animations to run whenever items are added to or removed from that container. To specify - * custom animations, use the {@link LayoutTransition#setAnimatable(int, Animatable) - * setAnimatable()} method. + * custom animations, use the {@link LayoutTransition#setAnimator(int, Animator) + * setAnimator()} method. * *

One of the core concepts of these transition animations is that there are two core * changes that cause the transition and four different animations that run because of @@ -61,7 +61,7 @@ import java.util.List; * CHANGE_APPEARING animation animates left, top, right, * and bottom. Values for these properties are updated with the pre- and post-layout * values when the transition begins. Custom animations will be similarly populated with - * the target and values being animated, assuming they use PropertyAnimator objects with + * the target and values being animated, assuming they use ObjectAnimator objects with * property names that are known on the target object.

*/ public class LayoutTransition { @@ -93,21 +93,21 @@ public class LayoutTransition { /** * These variables hold the animations that are currently used to run the transition effects. * These animations are set to defaults, but can be changed to custom animations by - * calls to setAnimatable(). + * calls to setAnimator(). */ - private Animatable mDisappearingAnim = null; - private Animatable mAppearingAnim = null; - private Animatable mChangingAppearingAnim = null; - private Animatable mChangingDisappearingAnim = null; + private Animator mDisappearingAnim = null; + private Animator mAppearingAnim = null; + private Animator mChangingAppearingAnim = null; + private Animator mChangingDisappearingAnim = null; /** * These are the default animations, defined in the constructor, that will be used * unless the user specifies custom animations. */ - private static PropertyAnimator defaultChangeIn; - private static PropertyAnimator defaultChangeOut; - private static PropertyAnimator defaultFadeIn; - private static PropertyAnimator defaultFadeOut; + private static ObjectAnimator defaultChangeIn; + private static ObjectAnimator defaultChangeOut; + private static ObjectAnimator defaultFadeIn; + private static ObjectAnimator defaultFadeOut; /** * The default duration used by all animations. @@ -154,7 +154,7 @@ public class LayoutTransition { * we cache all of the current animations in this map for possible cancellation on * another layout event. */ - private HashMap currentAnimations = new HashMap(); + private HashMap currentAnimations = new HashMap(); /** * This hashmap is used to track the listeners that have been added to the children of @@ -194,7 +194,7 @@ public class LayoutTransition { PropertyValuesHolder pvhTop = new PropertyValuesHolder("top", 0, 1); PropertyValuesHolder pvhRight = new PropertyValuesHolder("right", 0, 1); PropertyValuesHolder pvhBottom = new PropertyValuesHolder("bottom", 0, 1); - defaultChangeIn = new PropertyAnimator(DEFAULT_DURATION, this, + defaultChangeIn = new ObjectAnimator(DEFAULT_DURATION, this, pvhLeft, pvhTop, pvhRight, pvhBottom); defaultChangeIn.setStartDelay(mChangingAppearingDelay); defaultChangeIn.setInterpolator(mChangingAppearingInterpolator); @@ -202,11 +202,11 @@ public class LayoutTransition { defaultChangeOut.setStartDelay(mChangingDisappearingDelay); defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator); defaultFadeIn = - new PropertyAnimator(DEFAULT_DURATION, this, "alpha", 0f, 1f); + new ObjectAnimator(DEFAULT_DURATION, this, "alpha", 0f, 1f); defaultFadeIn.setStartDelay(mAppearingDelay); defaultFadeIn.setInterpolator(mAppearingInterpolator); defaultFadeOut = - new PropertyAnimator(DEFAULT_DURATION, this, "alpha", 1f, 0f); + new ObjectAnimator(DEFAULT_DURATION, this, "alpha", 1f, 0f); defaultFadeOut.setStartDelay(mDisappearingDelay); defaultFadeOut.setInterpolator(mDisappearingInterpolator); } @@ -240,7 +240,7 @@ public class LayoutTransition { * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start * delay is being set. * @param delay The length of time, in milliseconds, to delay before starting the animation. - * @see android.animation.Animatable#setStartDelay(long) + * @see Animator#setStartDelay(long) */ public void setStartDelay(int transitionType, long delay) { switch (transitionType) { @@ -268,7 +268,7 @@ public class LayoutTransition { * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start * delay is returned. * @return long The start delay of the specified animation. - * @see android.animation.Animatable#getStartDelay() + * @see Animator#getStartDelay() */ public long getStartDelay(int transitionType) { switch (transitionType) { @@ -294,7 +294,7 @@ public class LayoutTransition { * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose * duration is being set. * @param duration The length of time, in milliseconds, that the specified animation should run. - * @see android.animation.Animatable#setDuration(long) + * @see Animator#setDuration(long) */ public void setDuration(int transitionType, long duration) { switch (transitionType) { @@ -322,7 +322,7 @@ public class LayoutTransition { * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose * duration is returned. * @return long The duration of the specified animation. - * @see android.animation.Animatable#getDuration() + * @see Animator#getDuration() */ public long getDuration(int transitionType) { switch (transitionType) { @@ -387,7 +387,7 @@ public class LayoutTransition { * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose * duration is being set. * @param interpolator The interpolator that the specified animation should use. - * @see android.animation.Animatable#setInterpolator(android.view.animation.Interpolator) + * @see Animator#setInterpolator(android.view.animation.Interpolator) */ public void setInterpolator(int transitionType, Interpolator interpolator) { switch (transitionType) { @@ -415,7 +415,7 @@ public class LayoutTransition { * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose * duration is being set. * @return Interpolator The interpolator that the specified animation uses. - * @see android.animation.Animatable#setInterpolator(android.view.animation.Interpolator) + * @see Animator#setInterpolator(android.view.animation.Interpolator) */ public Interpolator getInterpolator(int transitionType) { switch (transitionType) { @@ -434,11 +434,11 @@ public class LayoutTransition { /** * Sets the animation used during one of the transition types that may run. Any - * Animatable object can be used, but to be most useful in the context of layout - * transitions, the animation should either be a PropertyAnimator or a Sequencer - * of animations including PropertyAnimators. Also, these PropertyAnimator objects + * Animator object can be used, but to be most useful in the context of layout + * transitions, the animation should either be a ObjectAnimator or a AnimatorSet + * of animations including PropertyAnimators. Also, these ObjectAnimator objects * should be able to get and set values on their target objects automatically. For - * example, a PropertyAnimator that animates the property "left" is able to set and get the + * example, a ObjectAnimator that animates the property "left" is able to set and get the * left property from the View objects being animated by the layout * transition. The transition works by setting target objects and properties * dynamically, according to the pre- and post-layoout values of those objects, so @@ -454,26 +454,26 @@ public class LayoutTransition { * object (presumably 1) as its starting and ending value when the animation begins. * Animations which need to use values at the beginning and end that may not match the * values queried when the transition begins may need to use a different mechanism - * than a standard PropertyAnimator object.

+ * than a standard ObjectAnimator object.

* * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose * duration is being set. - * @param animatable The animation being assigned. + * @param animator The animation being assigned. */ - public void setAnimatable(int transitionType, Animatable animatable) { + public void setAnimator(int transitionType, Animator animator) { switch (transitionType) { case CHANGE_APPEARING: - mChangingAppearingAnim = (animatable != null) ? animatable : defaultChangeIn; + mChangingAppearingAnim = (animator != null) ? animator : defaultChangeIn; break; case CHANGE_DISAPPEARING: - mChangingDisappearingAnim = (animatable != null) ? animatable : defaultChangeOut; + mChangingDisappearingAnim = (animator != null) ? animator : defaultChangeOut; break; case APPEARING: - mAppearingAnim = (animatable != null) ? animatable : defaultFadeIn; + mAppearingAnim = (animator != null) ? animator : defaultFadeIn; break; case DISAPPEARING: - mDisappearingAnim = (animatable != null) ? animatable : defaultFadeOut; + mDisappearingAnim = (animator != null) ? animator : defaultFadeOut; break; } } @@ -484,10 +484,10 @@ public class LayoutTransition { * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose * duration is being set. - * @return Animatable The animation being used for the given transition type. - * @see #setAnimatable(int, Animatable) + * @return Animator The animation being used for the given transition type. + * @see #setAnimator(int, Animator) */ - public Animatable getAnimatable(int transitionType) { + public Animator getAnimator(int transitionType) { switch (transitionType) { case CHANGE_APPEARING: return mChangingAppearingAnim; @@ -529,21 +529,21 @@ public class LayoutTransition { if (child != newView) { // If there's an animation running on this view already, cancel it - Animatable currentAnimation = currentAnimations.get(child); + Animator currentAnimation = currentAnimations.get(child); if (currentAnimation != null) { currentAnimation.cancel(); currentAnimations.remove(child); } // Make a copy of the appropriate animation - final Animatable anim = (changeReason == APPEARING) ? + final Animator anim = (changeReason == APPEARING) ? mChangingAppearingAnim.clone() : mChangingDisappearingAnim.clone(); // Set the target object for the animation anim.setTarget(child); - // A PropertyAnimator (or Sequencer of them) can extract start values from + // A ObjectAnimator (or AnimatorSet of them) can extract start values from // its target object anim.setupStartValues(); @@ -574,20 +574,20 @@ public class LayoutTransition { anim.setDuration(duration); // Remove the animation from the cache when it ends - anim.addListener(new AnimatableListenerAdapter() { + anim.addListener(new AnimatorListenerAdapter() { private boolean canceled = false; - public void onAnimationCancel(Animatable animatable) { + public void onAnimationCancel(Animator animator) { // we remove canceled animations immediately, not here canceled = true; } - public void onAnimationEnd(Animatable animatable) { + public void onAnimationEnd(Animator animator) { if (!canceled) { currentAnimations.remove(child); } } }); - if (anim instanceof PropertyAnimator) { - ((PropertyAnimator) anim).setCurrentPlayTime(0); + if (anim instanceof ObjectAnimator) { + ((ObjectAnimator) anim).setCurrentPlayTime(0); } anim.start(); @@ -626,15 +626,15 @@ public class LayoutTransition { * @param child The View being added to the ViewGroup. */ private void runAppearingTransition(final ViewGroup parent, final View child) { - Animatable anim = mAppearingAnim.clone(); + Animator anim = mAppearingAnim.clone(); anim.setTarget(child); anim.setStartDelay(mAppearingDelay); anim.setDuration(mAppearingDuration); - if (anim instanceof PropertyAnimator) { - ((PropertyAnimator) anim).setCurrentPlayTime(0); + if (anim instanceof ObjectAnimator) { + ((ObjectAnimator) anim).setCurrentPlayTime(0); } if (mListeners != null) { - anim.addListener(new AnimatableListenerAdapter() { + anim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd() { for (TransitionListener listener : mListeners) { listener.endTransition(LayoutTransition.this, parent, child, APPEARING); @@ -652,12 +652,12 @@ public class LayoutTransition { * @param child The View being removed from the ViewGroup. */ private void runDisappearingTransition(final ViewGroup parent, final View child) { - Animatable anim = mDisappearingAnim.clone(); + Animator anim = mDisappearingAnim.clone(); anim.setStartDelay(mDisappearingDelay); anim.setDuration(mDisappearingDuration); anim.setTarget(child); if (mListeners != null) { - anim.addListener(new AnimatableListenerAdapter() { + anim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd() { for (TransitionListener listener : mListeners) { listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); @@ -665,8 +665,8 @@ public class LayoutTransition { } }); } - if (anim instanceof PropertyAnimator) { - ((PropertyAnimator) anim).setCurrentPlayTime(0); + if (anim instanceof ObjectAnimator) { + ((ObjectAnimator) anim).setCurrentPlayTime(0); } anim.start(); } diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java new file mode 100644 index 0000000..6cb90be --- /dev/null +++ b/core/java/android/animation/ObjectAnimator.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2010 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 android.util.Log; + +import java.lang.reflect.Method; + +/** + * This subclass of {@link ValueAnimator} provides support for animating properties on target objects. + * The constructors of this class take parameters to define the target object that will be animated + * as well as the name of the property that will be animated. Appropriate set/get functions + * are then determined internally and the animation will call these functions as necessary to + * animate the property. + */ +public final class ObjectAnimator extends ValueAnimator { + + // The target object on which the property exists, set in the constructor + private Object mTarget; + + private String mPropertyName; + + /** + * 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 foo will result + * in a call to the function setFoo() on the target object. If either + * valueFrom or valueTo is null, then a getter function will + * also be derived and called. + * + *

Note that the setter function derived from this property name + * must take the same parameter type as the + * valueFrom and valueTo properties, otherwise the call to + * the setter function will fail.

+ * + *

If this ObjectAnimator has been set up to animate several properties together, + * using more than one PropertyValuesHolder objects, then setting the propertyName simply + * sets the propertyName in the first of those PropertyValuesHolder objects.

+ * + * @param propertyName The name of the property being animated. + */ + public void setPropertyName(String propertyName) { + if (mValues != null) { + // mValues should always be non-null + PropertyValuesHolder valuesHolder = mValues[0]; + String oldName = valuesHolder.getPropertyName(); + valuesHolder.setPropertyName(propertyName); + mValuesMap.remove(oldName); + mValuesMap.put(propertyName, valuesHolder); + } + mPropertyName = propertyName; + } + + /** + * Gets the name of the property that will be animated. This name will be used to derive + * a setter function that will be called to set animated values. + * For example, a property name of foo will result + * in a call to the function setFoo() on the target object. If either + * valueFrom or valueTo is null, then a getter function will + * also be derived and called. + */ + public String getPropertyName() { + return mPropertyName; + } + + /** + * Determine the setter or getter function using the JavaBeans convention of setFoo or + * getFoo for a property named 'foo'. This function figures out what the name of the + * function should be and uses reflection to find the Method with that name on the + * target object. + * + * @param prefix "set" or "get", depending on whether we need a setter or getter. + * @return Method the method associated with mPropertyName. + */ + private Method getPropertyFunction(String prefix, Class valueType) { + // TODO: faster implementation... + Method returnVal = null; + String firstLetter = mPropertyName.substring(0, 1); + String theRest = mPropertyName.substring(1); + firstLetter = firstLetter.toUpperCase(); + String setterName = prefix + firstLetter + theRest; + Class args[] = null; + if (valueType != null) { + args = new Class[1]; + args[0] = valueType; + } + try { + returnVal = mTarget.getClass().getMethod(setterName, args); + } catch (NoSuchMethodException e) { + Log.e("ObjectAnimator", + "Couldn't find setter/getter for property " + mPropertyName + ": " + e); + } + return returnVal; + } + + /** + * Creates a new ObjectAnimator object. This default constructor is primarily for + * use internally; the other constructors which take parameters are more generally + * useful. + */ + public ObjectAnimator() { + } + + /** + * A constructor that takes a single property name and set of values. This constructor is + * used in the simple case of animating a single property. + * + * @param duration The length of the animation, in milliseconds. + * @param target The object whose property is to be animated. This object should + * have a public method on it called setName(), where name is + * the value of the propertyName parameter. + * @param propertyName The name of the property being animated. + * @param values The set of values to animate between. If there is only one value, it + * is assumed to be the final value being animated to, and the initial value will be + * derived on the fly. + */ + public ObjectAnimator(long duration, Object target, String propertyName, T...values) { + super(duration, (T[]) values); + mTarget = target; + setPropertyName(propertyName); + } + + /** + * A constructor that takes PropertyValueHolder values. This constructor should + * be used when animating several properties at once with the same ObjectAnimator, since + * PropertyValuesHolder allows you to associate a set of animation values with a property + * name. + * + * @param duration The length of the animation, in milliseconds. + * @param target The object whose property is to be animated. This object should + * have public methods on it called setName(), where name is + * the name of the property passed in as the propertyName parameter for + * each of the PropertyValuesHolder objects. + * @param values The PropertyValuesHolder objects which hold each the property name and values + * to animate that property between. + */ + public ObjectAnimator(long duration, Object target, PropertyValuesHolder...values) { + super(duration); + setValues(values); + mTarget = target; + } + + /** + * This function is called immediately before processing the first animation + * frame of an animation. If there is a nonzero startDelay, the + * function is called after that delay ends. + * It takes care of the final initialization steps for the + * animation. This includes setting mEvaluator, if the user has not yet + * set it up, and the setter/getter methods, if the user did not supply + * them. + * + *

Overriders of this method should call the superclass method to cause + * internal mechanisms to be set up correctly.

+ */ + @Override + void initAnimation() { + if (!mInitialized) { + // mValueType may change due to setter/getter setup; do this before calling super.init(), + // which uses mValueType to set up the default type evaluator. + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setupSetterAndGetter(mTarget); + } + super.initAnimation(); + } + } + + + /** + * The target object whose property will be animated by this animation + * + * @return The object being animated + */ + public Object getTarget() { + return mTarget; + } + + /** + * Sets the target object whose property will be animated by this animation + * + * @param target The object being animated + */ + @Override + public void setTarget(Object target) { + mTarget = target; + } + + @Override + public void setupStartValues() { + initAnimation(); + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setupStartValue(mTarget); + } + } + + @Override + public void setupEndValues() { + initAnimation(); + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setupEndValue(mTarget); + } + } + + /** + * This method is called with the elapsed fraction of the animation during every + * animation frame. This function turns the elapsed fraction into an interpolated fraction + * and then into an animated value (from the evaluator. The function is called mostly during + * animation updates, but it is also called when the end() + * function is called, to set the final value on the property. + * + *

Overrides of this method must call the superclass to perform the calculation + * of the animated value.

+ * + * @param fraction The elapsed fraction of the animation. + */ + @Override + void animateValue(float fraction) { + super.animateValue(fraction); + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].setAnimatedValue(mTarget); + } + } + + @Override + public ObjectAnimator clone() { + final ObjectAnimator anim = (ObjectAnimator) super.clone(); + return anim; + } +} diff --git a/core/java/android/animation/PropertyAnimator.java b/core/java/android/animation/PropertyAnimator.java deleted file mode 100644 index e555cc6..0000000 --- a/core/java/android/animation/PropertyAnimator.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2010 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 android.util.Log; - -import java.lang.reflect.Method; - -/** - * This subclass of {@link Animator} provides support for animating properties on target objects. - * The constructors of this class take parameters to define the target object that will be animated - * as well as the name of the property that will be animated. Appropriate set/get functions - * are then determined internally and the animation will call these functions as necessary to - * animate the property. - */ -public final class PropertyAnimator extends Animator { - - // The target object on which the property exists, set in the constructor - private Object mTarget; - - private String mPropertyName; - - /** - * 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 foo will result - * in a call to the function setFoo() on the target object. If either - * valueFrom or valueTo is null, then a getter function will - * also be derived and called. - * - *

Note that the setter function derived from this property name - * must take the same parameter type as the - * valueFrom and valueTo properties, otherwise the call to - * the setter function will fail.

- * - *

If this PropertyAnimator has been set up to animate several properties together, - * using more than one PropertyValuesHolder objects, then setting the propertyName simply - * sets the propertyName in the first of those PropertyValuesHolder objects.

- * - * @param propertyName The name of the property being animated. - */ - public void setPropertyName(String propertyName) { - if (mValues != null) { - // mValues should always be non-null - PropertyValuesHolder valuesHolder = mValues[0]; - String oldName = valuesHolder.getPropertyName(); - valuesHolder.setPropertyName(propertyName); - mValuesMap.remove(oldName); - mValuesMap.put(propertyName, valuesHolder); - } - mPropertyName = propertyName; - } - - /** - * Gets the name of the property that will be animated. This name will be used to derive - * a setter function that will be called to set animated values. - * For example, a property name of foo will result - * in a call to the function setFoo() on the target object. If either - * valueFrom or valueTo is null, then a getter function will - * also be derived and called. - */ - public String getPropertyName() { - return mPropertyName; - } - - /** - * Determine the setter or getter function using the JavaBeans convention of setFoo or - * getFoo for a property named 'foo'. This function figures out what the name of the - * function should be and uses reflection to find the Method with that name on the - * target object. - * - * @param prefix "set" or "get", depending on whether we need a setter or getter. - * @return Method the method associated with mPropertyName. - */ - private Method getPropertyFunction(String prefix, Class valueType) { - // TODO: faster implementation... - Method returnVal = null; - String firstLetter = mPropertyName.substring(0, 1); - String theRest = mPropertyName.substring(1); - firstLetter = firstLetter.toUpperCase(); - String setterName = prefix + firstLetter + theRest; - Class args[] = null; - if (valueType != null) { - args = new Class[1]; - args[0] = valueType; - } - try { - returnVal = mTarget.getClass().getMethod(setterName, args); - } catch (NoSuchMethodException e) { - Log.e("PropertyAnimator", - "Couldn't find setter/getter for property " + mPropertyName + ": " + e); - } - return returnVal; - } - - /** - * Creates a new PropertyAnimator object. This default constructor is primarily for - * use internally; the other constructors which take parameters are more generally - * useful. - */ - public PropertyAnimator() { - } - - /** - * A constructor that takes a single property name and set of values. This constructor is - * used in the simple case of animating a single property. - * - * @param duration The length of the animation, in milliseconds. - * @param target The object whose property is to be animated. This object should - * have a public method on it called setName(), where name is - * the value of the propertyName parameter. - * @param propertyName The name of the property being animated. - * @param values The set of values to animate between. If there is only one value, it - * is assumed to be the final value being animated to, and the initial value will be - * derived on the fly. - */ - public PropertyAnimator(long duration, Object target, String propertyName, T...values) { - super(duration, (T[]) values); - mTarget = target; - setPropertyName(propertyName); - } - - /** - * A constructor that takes PropertyValueHolder values. This constructor should - * be used when animating several properties at once with the same PropertyAnimator, since - * PropertyValuesHolder allows you to associate a set of animation values with a property - * name. - * - * @param duration The length of the animation, in milliseconds. - * @param target The object whose property is to be animated. This object should - * have public methods on it called setName(), where name is - * the name of the property passed in as the propertyName parameter for - * each of the PropertyValuesHolder objects. - * @param values The PropertyValuesHolder objects which hold each the property name and values - * to animate that property between. - */ - public PropertyAnimator(long duration, Object target, PropertyValuesHolder...values) { - super(duration); - setValues(values); - mTarget = target; - } - - /** - * This function is called immediately before processing the first animation - * frame of an animation. If there is a nonzero startDelay, the - * function is called after that delay ends. - * It takes care of the final initialization steps for the - * animation. This includes setting mEvaluator, if the user has not yet - * set it up, and the setter/getter methods, if the user did not supply - * them. - * - *

Overriders of this method should call the superclass method to cause - * internal mechanisms to be set up correctly.

- */ - @Override - void initAnimation() { - if (!mInitialized) { - // mValueType may change due to setter/getter setup; do this before calling super.init(), - // which uses mValueType to set up the default type evaluator. - int numValues = mValues.length; - for (int i = 0; i < numValues; ++i) { - mValues[i].setupSetterAndGetter(mTarget); - } - super.initAnimation(); - } - } - - - /** - * The target object whose property will be animated by this animation - * - * @return The object being animated - */ - public Object getTarget() { - return mTarget; - } - - /** - * Sets the target object whose property will be animated by this animation - * - * @param target The object being animated - */ - @Override - public void setTarget(Object target) { - mTarget = target; - } - - @Override - public void setupStartValues() { - initAnimation(); - int numValues = mValues.length; - for (int i = 0; i < numValues; ++i) { - mValues[i].setupStartValue(mTarget); - } - } - - @Override - public void setupEndValues() { - initAnimation(); - int numValues = mValues.length; - for (int i = 0; i < numValues; ++i) { - mValues[i].setupEndValue(mTarget); - } - } - - /** - * This method is called with the elapsed fraction of the animation during every - * animation frame. This function turns the elapsed fraction into an interpolated fraction - * and then into an animated value (from the evaluator. The function is called mostly during - * animation updates, but it is also called when the end() - * function is called, to set the final value on the property. - * - *

Overrides of this method must call the superclass to perform the calculation - * of the animated value.

- * - * @param fraction The elapsed fraction of the animation. - */ - @Override - void animateValue(float fraction) { - super.animateValue(fraction); - int numValues = mValues.length; - for (int i = 0; i < numValues; ++i) { - mValues[i].setAnimatedValue(mTarget); - } - } - - @Override - public PropertyAnimator clone() { - final PropertyAnimator anim = (PropertyAnimator) super.clone(); - return anim; - } -} diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java index b6ff54e..1d46123 100644 --- a/core/java/android/animation/PropertyValuesHolder.java +++ b/core/java/android/animation/PropertyValuesHolder.java @@ -27,31 +27,31 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; /** * This class holds information about a property and the values that that property * should take on during an animation. PropertyValuesHolder objects can be used to create - * animations with Animator or PropertyAnimator that operate on several different properties + * animations with ValueAnimator or ObjectAnimator that operate on several different properties * in parallel. */ public class PropertyValuesHolder implements Cloneable { /** * The name of the property associated with the values. This need not be a real property, - * unless this object is being used with PropertyAnimator. But this is the name by which - * aniamted values are looked up with getAnimatedValue(String) in Animator. + * unless this object is being used with ObjectAnimator. But this is the name by which + * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator. */ private String mPropertyName; /** - * The setter function, if needed. PropertyAnimator hands off this functionality to + * The setter function, if needed. ObjectAnimator hands off this functionality to * PropertyValuesHolder, since it holds all of the per-property information. This * property can be manually set via setSetter(). Otherwise, it is automatically - * derived when the animation starts in setupSetterAndGetter() if using PropertyAnimator. + * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator. */ private Method mSetter = null; /** - * The getter function, if needed. PropertyAnimator hands off this functionality to + * The getter function, if needed. ObjectAnimator hands off this functionality to * PropertyValuesHolder, since it holds all of the per-property information. This * property can be manually set via setSetter(). Otherwise, it is automatically - * derived when the animation starts in setupSetterAndGetter() if using PropertyAnimator. + * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator. * The getter is only derived and used if one of the values is null. */ private Method mGetter = null; @@ -112,16 +112,16 @@ public class PropertyValuesHolder implements Cloneable { /** * The value most recently calculated by calculateValue(). This is set during - * that function and might be retrieved later either by Animator.animatedValue() or - * by the property-setting logic in PropertyAnimator.animatedValue(). + * that function and might be retrieved later either by ValueAnimator.animatedValue() or + * by the property-setting logic in ObjectAnimator.animatedValue(). */ private Object mAnimatedValue; /** * Constructs a PropertyValuesHolder object with just a set of values. This constructor - * is typically not used when animating objects with PropertyAnimator, because that + * is typically not used when animating objects with ObjectAnimator, because that * object needs distinct and meaningful property names. Simpler animations of one - * set of values using Animator may use this constructor, however, because no + * set of values using ValueAnimator may use this constructor, however, because no * distinguishing name is needed. * @param values The set of values to animate between. If there is only one value, it * is assumed to be the final value being animated to, and the initial value will be @@ -141,14 +141,14 @@ public class PropertyValuesHolder implements Cloneable { * on the object. Also, if any value is null, the value will be filled in when the animation * starts in the same way. This mechanism of automatically getting null values only works * if the PropertyValuesHolder object is used in conjunction - * {@link android.animation.PropertyAnimator}, and with a getter function either + * {@link ObjectAnimator}, and with a getter function either * derived automatically from propertyName or set explicitly via * {@link #setGetter(java.lang.reflect.Method)}, since otherwise PropertyValuesHolder has * no way of determining what the value should be. * @param propertyName The name of the property associated with this set of values. This - * can be the actual property name to be used when using a PropertyAnimator object, or + * can be the actual property name to be used when using a ObjectAnimator object, or * just a name used to get animated values, such as if this object is used with an - * Animator object. + * ValueAnimator object. * @param values The set of values to animate between. */ public PropertyValuesHolder(String propertyName, T... values) { @@ -163,7 +163,7 @@ public class PropertyValuesHolder implements Cloneable { * on the object. Also, if any value is null, the value will be filled in when the animation * starts in the same way. This mechanism of automatically getting null values only works * if the PropertyValuesHolder object is used in conjunction - * {@link android.animation.PropertyAnimator}, and with a getter function either + * {@link ObjectAnimator}, and with a getter function either * derived automatically from propertyName or set explicitly via * {@link #setGetter(java.lang.reflect.Method)}, since otherwise PropertyValuesHolder has * no way of determining what the value should be. @@ -331,7 +331,7 @@ public class PropertyValuesHolder implements Cloneable { } /** - * Internal function (called from PropertyAnimator) to set up the setter and getter + * Internal function (called from ObjectAnimator) to set up the setter and getter * prior to running the animation. If the setter has not been manually set for this * object, it will be derived automatically given the property name, target object, and * types of values supplied. If no getter has been set, it will be supplied iff any of the @@ -383,7 +383,7 @@ public class PropertyValuesHolder implements Cloneable { } /** - * This function is called by PropertyAnimator when setting the start values for an animation. + * This function is called by ObjectAnimator when setting the start values for an animation. * The start values are set according to the current values in the target object. The * property whose value is extracted is whatever is specified by the propertyName of this * PropertyValuesHolder object. @@ -395,7 +395,7 @@ public class PropertyValuesHolder implements Cloneable { } /** - * This function is called by PropertyAnimator when setting the end values for an animation. + * This function is called by ObjectAnimator when setting the end values for an animation. * The end values are set according to the current values in the target object. The * property whose value is extracted is whatever is specified by the propertyName of this * PropertyValuesHolder object. @@ -420,8 +420,8 @@ public class PropertyValuesHolder implements Cloneable { } /** * Internal function to set the value on the target object, using the setter set up - * earlier on this PropertyValuesHolder object. This function is called by PropertyAnimator - * to handle turning the value calculated by Animator into a value set on the object + * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator + * to handle turning the value calculated by ValueAnimator into a value set on the object * according to the name of the property. * @param target The target object on which the value is set */ @@ -439,7 +439,7 @@ public class PropertyValuesHolder implements Cloneable { } /** - * Internal function, called by Animator, to set up the TypeEvaluator that will be used + * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used * to calculate animated values. */ void init() { @@ -466,7 +466,7 @@ public class PropertyValuesHolder implements Cloneable { /** * Function used to calculate the value according to the evaluator set up for - * this PropertyValuesHolder object. This function is called by Animator.animateValue(). + * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue(). * * @param fraction The elapsed, interpolated fraction of the animation. * @return The calculated value at this point in the animation. @@ -483,7 +483,7 @@ public class PropertyValuesHolder implements Cloneable { * approach is more direct, and is especially useful when a function must be called that does * not correspond to the convention of setName(). For example, if a function * called offset() is to be called with the animated values, there is no way - * to tell PropertyAnimator how to call that function simply through a property + * to tell ObjectAnimator how to call that function simply through a property * name, so a setter method should be supplied instead. * *

Note that the setter function must take the same parameter type as the @@ -511,7 +511,7 @@ public class PropertyValuesHolder implements Cloneable { * approach is more direct, and is especially useful when a function must be called that does * not correspond to the convention of setName(). For example, if a function * called offset() is to be called to get an initial value, there is no way - * to tell PropertyAnimator how to call that function simply through a property + * to tell ObjectAnimator how to call that function simply through a property * name, so a getter method should be supplied instead. * *

Note that the getter method is only called whether supplied here or derived @@ -569,7 +569,7 @@ public class PropertyValuesHolder implements Cloneable { } /** - * Internal function, called by Animator and PropertyAnimator, to retrieve the value + * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value * most recently calculated in calculateValue(). * @return */ diff --git a/core/java/android/animation/Sequencer.java b/core/java/android/animation/Sequencer.java deleted file mode 100644 index 04bede0..0000000 --- a/core/java/android/animation/Sequencer.java +++ /dev/null @@ -1,943 +0,0 @@ -/* - * Copyright (C) 2010 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 android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * This class plays a set of {@link Animatable} objects in the specified order. Animations - * can be set up to play together, in sequence, or after a specified delay. - * - *

There are two different approaches to adding animations to a Sequencer: - * either the {@link Sequencer#playTogether(Animatable[]) playTogether()} or - * {@link Sequencer#playSequentially(Animatable[]) playSequentially()} methods can be called to add - * a set of animations all at once, or the {@link Sequencer#play(Animatable)} can be - * used in conjunction with methods in the {@link android.animation.Sequencer.Builder Builder} - * class to add animations - * one by one.

- * - *

It is possible to set up a Sequencer with circular dependencies between - * its animations. For example, an animation a1 could be set up to start before animation a2, a2 - * before a3, and a3 before a1. The results of this configuration are undefined, but will typically - * result in none of the affected animations being played. Because of this (and because - * circular dependencies do not make logical sense anyway), circular dependencies - * should be avoided, and the dependency flow of animations should only be in one direction. - */ -public final class Sequencer extends Animatable { - - /** - * Internal variables - * NOTE: This object implements the clone() method, making a deep copy of any referenced - * objects. As other non-trivial fields are added to this class, make sure to add logic - * to clone() to make deep copies of them. - */ - - /** - * Tracks animations currently being played, so that we know what to - * cancel or end when cancel() or end() is called on this Sequencer - */ - private ArrayList mPlayingSet = new ArrayList(); - - /** - * Contains all nodes, mapped to their respective Animatables. When new - * dependency information is added for an Animatable, we want to add it - * to a single node representing that Animatable, not create a new Node - * if one already exists. - */ - private HashMap mNodeMap = new HashMap(); - - /** - * Set of all nodes created for this Sequencer. This list is used upon - * starting the sequencer, and the nodes are placed in sorted order into the - * sortedNodes collection. - */ - private ArrayList mNodes = new ArrayList(); - - /** - * The sorted list of nodes. This is the order in which the animations will - * be played. The details about when exactly they will be played depend - * on the dependency relationships of the nodes. - */ - private ArrayList mSortedNodes = new ArrayList(); - - /** - * Flag indicating whether the nodes should be sorted prior to playing. This - * flag allows us to cache the previous sorted nodes so that if the sequence - * is replayed with no changes, it does not have to re-sort the nodes again. - */ - private boolean mNeedsSort = true; - - private SequencerAnimatableListener mSequenceListener = null; - - /** - * Flag indicating that the Sequencer has been canceled (by calling cancel() or end()). - * This flag is used to avoid starting other animations when currently-playing - * child animations of this Sequencer end. - */ - boolean mCanceled = false; - - // The amount of time in ms to delay starting the animation after start() is called - private long mStartDelay = 0; - - - // 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 Sequencer. When a real duration is - // set, it is passed along to the child animations. - private long mDuration = -1; - - - /** - * Sets up this Sequencer to play all of the supplied animations at the same time. - * - * @param sequenceItems The animations that will be started simultaneously. - */ - public void playTogether(Animatable... sequenceItems) { - if (sequenceItems != null) { - mNeedsSort = true; - Builder builder = play(sequenceItems[0]); - for (int i = 1; i < sequenceItems.length; ++i) { - builder.with(sequenceItems[i]); - } - } - } - - /** - * Sets up this Sequencer to play each of the supplied animations when the - * previous animation ends. - * - * @param sequenceItems The aniamtions that will be started one after another. - */ - public void playSequentially(Animatable... sequenceItems) { - if (sequenceItems != null) { - mNeedsSort = true; - if (sequenceItems.length == 1) { - play(sequenceItems[0]); - } else { - for (int i = 0; i < sequenceItems.length - 1; ++i) { - play(sequenceItems[i]).before(sequenceItems[i+1]); - } - } - } - } - - /** - * Returns the current list of child Animatable objects controlled by this - * Sequencer. This is a copy of the internal list; modifications to the returned list - * will not affect the Sequencer, although changes to the underlying Animatable objects - * will affect those objects being managed by the Sequencer. - * - * @return ArrayList The list of child animations of this Sequencer. - */ - public ArrayList getChildAnimations() { - ArrayList childList = new ArrayList(); - for (Node node : mNodes) { - childList.add(node.animation); - } - return childList; - } - - /** - * Sets the target object for all current {@link #getChildAnimations() child animations} - * of this Sequencer that take targets ({@link android.animation.PropertyAnimator} and - * Sequencer). - * - * @param target The object being animated - */ - @Override - public void setTarget(Object target) { - for (Node node : mNodes) { - Animatable animation = node.animation; - if (animation instanceof Sequencer) { - ((Sequencer)animation).setTarget(target); - } else if (animation instanceof PropertyAnimator) { - ((PropertyAnimator)animation).setTarget(target); - } - } - } - - /** - * Sets the Interpolator for all current {@link #getChildAnimations() child animations} - * of this Sequencer. - * - * @param interpolator the interpolator to be used by each child animation of this Sequencer - */ - @Override - public void setInterpolator(Interpolator interpolator) { - for (Node node : mNodes) { - node.animation.setInterpolator(interpolator); - } - } - - /** - * This method creates a Builder object, which is used to - * set up playing constraints. This initial play() method - * tells the Builder the animation that is the dependency for - * the succeeding commands to the Builder. For example, - * calling play(a1).with(a2) sets up the Sequence to play - * a1 and a2 at the same time, - * play(a1).before(a2) sets up the Sequence to play - * a1 first, followed by a2, and - * play(a1).after(a2) sets up the Sequence to play - * a2 first, followed by a1. - * - *

Note that play() is the only way to tell the - * Builder the animation upon which the dependency is created, - * so successive calls to the various functions in Builder - * will all refer to the initial parameter supplied in play() - * as the dependency of the other animations. For example, calling - * play(a1).before(a2).before(a3) will play both a2 - * and a3 when a1 ends; it does not set up a dependency between - * a2 and a3.

- * - * @param anim The animation that is the dependency used in later calls to the - * methods in the returned Builder object. A null parameter will result - * in a null Builder return value. - * @return Builder The object that constructs the sequence based on the dependencies - * outlined in the calls to play and the other methods in the - * BuilderNote that canceling a Sequencer also cancels all of the animations that it is - * responsible for.

- */ - @SuppressWarnings("unchecked") - @Override - public void cancel() { - mCanceled = true; - if (mListeners != null) { - ArrayList tmpListeners = - (ArrayList) mListeners.clone(); - for (AnimatableListener listener : tmpListeners) { - listener.onAnimationCancel(this); - } - } - if (mSortedNodes.size() > 0) { - for (Node node : mSortedNodes) { - node.animation.cancel(); - } - } - } - - /** - * {@inheritDoc} - * - *

Note that ending a Sequencer also ends all of the animations that it is - * responsible for.

- */ - @Override - public void end() { - mCanceled = true; - if (mSortedNodes.size() != mNodes.size()) { - // hasn't been started yet - sort the nodes now, then end them - sortNodes(); - for (Node node : mSortedNodes) { - if (mSequenceListener == null) { - mSequenceListener = new SequencerAnimatableListener(this); - } - node.animation.addListener(mSequenceListener); - } - } - if (mSortedNodes.size() > 0) { - for (Node node : mSortedNodes) { - node.animation.end(); - } - } - } - - /** - * Returns true if any of the child animations of this Sequencer have been started and have not - * yet ended. - * @return Whether this Sequencer has been started and has not yet ended. - */ - @Override - public boolean isRunning() { - for (Node node : mNodes) { - if (node.animation.isRunning()) { - return true; - } - } - return false; - } - - /** - * The amount of time, in milliseconds, to delay starting the animation after - * {@link #start()} is called. - * - * @return the number of milliseconds to delay running the animation - */ - @Override - public long getStartDelay() { - return mStartDelay; - } - - /** - * The amount of time, in milliseconds, to delay starting the animation after - * {@link #start()} is called. - - * @param startDelay The amount of the delay, in milliseconds - */ - @Override - public void setStartDelay(long startDelay) { - mStartDelay = startDelay; - } - - /** - * Gets the length of each of the child animations of this Sequencer. This value may - * be less than 0, which indicates that no duration has been set on this Sequencer - * and each of the child animations will use their own duration. - * - * @return The length of the animation, in milliseconds, of each of the child - * animations of this Sequencer. - */ - @Override - public long getDuration() { - return mDuration; - } - - /** - * Sets the length of each of the current child animations of this Sequencer. By default, - * each child animation will use its own duration. If the duration is set on the Sequencer, - * then each child animation inherits this duration. - * - * @param duration The length of the animation, in milliseconds, of each of the child - * animations of this Sequencer. - */ - @Override - public void setDuration(long duration) { - if (duration < 0) { - throw new IllegalArgumentException("duration must be a value of zero or greater"); - } - for (Node node : mNodes) { - // TODO: don't set the duration of the timing-only nodes created by Sequencer to - // insert "play-after" delays - node.animation.setDuration(duration); - } - mDuration = duration; - } - - /** - * {@inheritDoc} - * - *

Starting this Sequencer will, in turn, start the animations for which - * it is responsible. The details of when exactly those animations are started depends on - * the dependency relationships that have been set up between the animations. - */ - @SuppressWarnings("unchecked") - @Override - public void start() { - mCanceled = false; - - // First, sort the nodes (if necessary). This will ensure that sortedNodes - // contains the animation nodes in the correct order. - sortNodes(); - - // 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 nodesToStart = new ArrayList(); - for (Node node : mSortedNodes) { - if (mSequenceListener == null) { - mSequenceListener = new SequencerAnimatableListener(this); - } - if (node.dependencies == null || node.dependencies.size() == 0) { - nodesToStart.add(node); - } else { - for (Dependency dependency : node.dependencies) { - dependency.node.animation.addListener( - new DependencyListener(this, node, dependency.rule)); - } - node.tmpDependencies = (ArrayList) node.dependencies.clone(); - } - node.animation.addListener(mSequenceListener); - } - // Now that all dependencies are set up, start the animations that should be started. - if (mStartDelay <= 0) { - for (Node node : nodesToStart) { - node.animation.start(); - mPlayingSet.add(node.animation); - } - } else { - // TODO: Need to cancel out of the delay appropriately - Animator delayAnim = new Animator(mStartDelay, 0f, 1f); - delayAnim.addListener(new AnimatableListenerAdapter() { - public void onAnimationEnd(Animatable anim) { - for (Node node : nodesToStart) { - node.animation.start(); - mPlayingSet.add(node.animation); - } - } - }); - } - if (mListeners != null) { - ArrayList tmpListeners = - (ArrayList) mListeners.clone(); - for (AnimatableListener listener : tmpListeners) { - listener.onAnimationStart(this); - } - } - } - - @Override - public Sequencer clone() { - final Sequencer anim = (Sequencer) super.clone(); - /* - * The basic clone() operation copies all items. This doesn't work very well for - * Sequencer, because it will copy references that need to be recreated and state - * that may not apply. What we need to do now is put the clone in an uninitialized - * state, with fresh, empty data structures. Then we will build up the nodes list - * manually, as we clone each Node (and its animation). The clone will then be sorted, - * and will populate any appropriate lists, when it is started. - */ - anim.mNeedsSort = true; - anim.mCanceled = false; - anim.mPlayingSet = new ArrayList(); - anim.mNodeMap = new HashMap(); - anim.mNodes = new ArrayList(); - anim.mSortedNodes = new ArrayList(); - - // Walk through the old nodes list, cloning each node and adding it to the new nodemap. - // One problem is that the old node dependencies point to nodes in the old sequencer. - // We need to track the old/new nodes in order to reconstruct the dependencies in the clone. - HashMap nodeCloneMap = new HashMap(); // - for (Node node : mNodes) { - Node nodeClone = node.clone(); - nodeCloneMap.put(node, nodeClone); - anim.mNodes.add(nodeClone); - anim.mNodeMap.put(nodeClone.animation, nodeClone); - // Clear out the dependencies in the clone; we'll set these up manually later - nodeClone.dependencies = null; - nodeClone.tmpDependencies = null; - nodeClone.nodeDependents = null; - nodeClone.nodeDependencies = null; - // clear out any listeners that were set up by the sequencer; these will - // be set up when the clone's nodes are sorted - ArrayList cloneListeners = nodeClone.animation.getListeners(); - if (cloneListeners != null) { - ArrayList listenersToRemove = null; - for (AnimatableListener listener : cloneListeners) { - if (listener instanceof SequencerAnimatableListener) { - if (listenersToRemove == null) { - listenersToRemove = new ArrayList(); - } - listenersToRemove.add(listener); - } - } - if (listenersToRemove != null) { - for (AnimatableListener listener : listenersToRemove) { - cloneListeners.remove(listener); - } - } - } - } - // Now that we've cloned all of the nodes, we're ready to walk through their - // dependencies, mapping the old dependencies to the new nodes - for (Node node : mNodes) { - Node nodeClone = nodeCloneMap.get(node); - if (node.dependencies != null) { - for (Dependency dependency : node.dependencies) { - Node clonedDependencyNode = nodeCloneMap.get(dependency.node); - Dependency cloneDependency = new Dependency(clonedDependencyNode, - dependency.rule); - nodeClone.addDependency(cloneDependency); - } - } - } - - return anim; - } - - /** - * This class is the mechanism by which animations are started based on events in other - * animations. If an animation has multiple dependencies on other animations, then - * all dependencies must be satisfied before the animation is started. - */ - private static class DependencyListener implements AnimatableListener { - - private Sequencer mSequencer; - - // The node upon which the dependency is based. - private Node mNode; - - // The Dependency rule (WITH or AFTER) that the listener should wait for on - // the node - private int mRule; - - public DependencyListener(Sequencer sequencer, Node node, int rule) { - this.mSequencer = sequencer; - this.mNode = node; - this.mRule = rule; - } - - /** - * Ignore cancel events for now. We may want to handle this eventually, - * to prevent follow-on animations from running when some dependency - * animation is canceled. - */ - public void onAnimationCancel(Animatable animation) { - } - - /** - * An end event is received - see if this is an event we are listening for - */ - public void onAnimationEnd(Animatable animation) { - if (mRule == Dependency.AFTER) { - startIfReady(animation); - } - } - - /** - * Ignore repeat events for now - */ - public void onAnimationRepeat(Animatable animation) { - } - - /** - * A start event is received - see if this is an event we are listening for - */ - public void onAnimationStart(Animatable animation) { - if (mRule == Dependency.WITH) { - startIfReady(animation); - } - } - - /** - * Check whether the event received is one that the node was waiting for. - * If so, mark it as complete and see whether it's time to start - * the animation. - * @param dependencyAnimation the animation that sent the event. - */ - private void startIfReady(Animatable dependencyAnimation) { - if (mSequencer.mCanceled) { - // if the parent Sequencer was canceled, then don't start any dependent anims - return; - } - Dependency dependencyToRemove = null; - for (Dependency dependency : mNode.tmpDependencies) { - if (dependency.rule == mRule && - dependency.node.animation == dependencyAnimation) { - // rule fired - remove the dependency and listener and check to - // see whether it's time to start the animation - dependencyToRemove = dependency; - dependencyAnimation.removeListener(this); - break; - } - } - mNode.tmpDependencies.remove(dependencyToRemove); - if (mNode.tmpDependencies.size() == 0) { - // all dependencies satisfied: start the animation - mNode.animation.start(); - mSequencer.mPlayingSet.add(mNode.animation); - } - } - - } - - private class SequencerAnimatableListener implements AnimatableListener { - - private Sequencer mSequencer; - - SequencerAnimatableListener(Sequencer sequencer) { - mSequencer = sequencer; - } - - public void onAnimationCancel(Animatable animation) { - if (mPlayingSet.size() == 0) { - if (mListeners != null) { - for (AnimatableListener listener : mListeners) { - listener.onAnimationCancel(mSequencer); - } - } - } - } - - @SuppressWarnings("unchecked") - public void onAnimationEnd(Animatable animation) { - animation.removeListener(this); - mPlayingSet.remove(animation); - Node animNode = mSequencer.mNodeMap.get(animation); - animNode.done = true; - ArrayList sortedNodes = mSequencer.mSortedNodes; - boolean allDone = true; - for (Node node : sortedNodes) { - if (!node.done) { - allDone = false; - break; - } - } - if (allDone) { - // If this was the last child animation to end, then notify listeners that this - // sequencer has ended - if (mListeners != null) { - ArrayList tmpListeners = - (ArrayList) mListeners.clone(); - for (AnimatableListener listener : tmpListeners) { - listener.onAnimationEnd(mSequencer); - } - } - } - } - - // Nothing to do - public void onAnimationRepeat(Animatable animation) { - } - - // Nothing to do - public void onAnimationStart(Animatable animation) { - } - - } - - /** - * This method sorts the current set of nodes, if needed. The sort is a simple - * DependencyGraph sort, which goes like this: - * - All nodes without dependencies become 'roots' - * - while roots list is not null - * - for each root r - * - add r to sorted list - * - remove r as a dependency from any other node - * - any nodes with no dependencies are added to the roots list - */ - private void sortNodes() { - if (mNeedsSort) { - mSortedNodes.clear(); - ArrayList roots = new ArrayList(); - for (Node node : mNodes) { - if (node.dependencies == null || node.dependencies.size() == 0) { - roots.add(node); - } - } - ArrayList tmpRoots = new ArrayList(); - while (roots.size() > 0) { - for (Node root : roots) { - mSortedNodes.add(root); - if (root.nodeDependents != null) { - for (Node node : root.nodeDependents) { - node.nodeDependencies.remove(root); - if (node.nodeDependencies.size() == 0) { - tmpRoots.add(node); - } - } - } - } - roots.clear(); - roots.addAll(tmpRoots); - tmpRoots.clear(); - } - mNeedsSort = false; - if (mSortedNodes.size() != mNodes.size()) { - throw new IllegalStateException("Circular dependencies cannot exist" - + " in Sequencer"); - } - } else { - // Doesn't need sorting, but still need to add in the nodeDependencies list - // because these get removed as the event listeners fire and the dependencies - // are satisfied - for (Node node : mNodes) { - if (node.dependencies != null && node.dependencies.size() > 0) { - for (Dependency dependency : node.dependencies) { - if (node.nodeDependencies == null) { - node.nodeDependencies = new ArrayList(); - } - if (!node.nodeDependencies.contains(dependency.node)) { - node.nodeDependencies.add(dependency.node); - } - } - } - node.done = false; - } - } - } - - /** - * Dependency holds information about the node that some other node is - * dependent upon and the nature of that dependency. - * - */ - private static class Dependency { - static final int WITH = 0; // dependent node must start with this dependency node - static final int AFTER = 1; // dependent node must start when this dependency node finishes - - // The node that the other node with this Dependency is dependent upon - public Node node; - - // The nature of the dependency (WITH or AFTER) - public int rule; - - public Dependency(Node node, int rule) { - this.node = node; - this.rule = rule; - } - } - - /** - * A Node is an embodiment of both the Animatable that it wraps as well as - * any dependencies that are associated with that Animation. This includes - * both dependencies upon other nodes (in the dependencies list) as - * well as dependencies of other nodes upon this (in the nodeDependents list). - */ - private static class Node implements Cloneable { - public Animatable animation; - - /** - * These are the dependencies that this node's animation has on other - * nodes. For example, if this node's animation should begin with some - * other animation ends, then there will be an item in this node's - * dependencies list for that other animation's node. - */ - public ArrayList dependencies = null; - - /** - * tmpDependencies is a runtime detail. We use the dependencies list for sorting. - * But we also use the list to keep track of when multiple dependencies are satisfied, - * but removing each dependency as it is satisfied. We do not want to remove - * the dependency itself from the list, because we need to retain that information - * if the sequencer is launched in the future. So we create a copy of the dependency - * list when the sequencer starts and use this tmpDependencies list to track the - * list of satisfied dependencies. - */ - public ArrayList tmpDependencies = null; - - /** - * nodeDependencies is just a list of the nodes that this Node is dependent upon. - * This information is used in sortNodes(), to determine when a node is a root. - */ - public ArrayList nodeDependencies = null; - - /** - * nodeDepdendents is the list of nodes that have this node as a dependency. This - * is a utility field used in sortNodes to facilitate removing this node as a - * dependency when it is a root node. - */ - public ArrayList nodeDependents = null; - - /** - * Flag indicating whether the animation in this node is finished. This flag - * is used by Sequencer to check, as each animation ends, whether all child animations - * are done and it's time to send out an end event for the entire Sequencer. - */ - public boolean done = false; - - /** - * Constructs the Node with the animation that it encapsulates. A Node has no - * dependencies by default; dependencies are added via the addDependency() - * method. - * - * @param animation The animation that the Node encapsulates. - */ - public Node(Animatable animation) { - this.animation = animation; - } - - /** - * Add a dependency to this Node. The dependency includes information about the - * node that this node is dependency upon and the nature of the dependency. - * @param dependency - */ - public void addDependency(Dependency dependency) { - if (dependencies == null) { - dependencies = new ArrayList(); - nodeDependencies = new ArrayList(); - } - dependencies.add(dependency); - if (!nodeDependencies.contains(dependency.node)) { - nodeDependencies.add(dependency.node); - } - Node dependencyNode = dependency.node; - if (dependencyNode.nodeDependents == null) { - dependencyNode.nodeDependents = new ArrayList(); - } - dependencyNode.nodeDependents.add(this); - } - - @Override - public Node clone() { - try { - Node node = (Node) super.clone(); - node.animation = (Animatable) animation.clone(); - return node; - } catch (CloneNotSupportedException e) { - throw new AssertionError(); - } - } - } - - /** - * The Builder object is a utility class to facilitate adding animations to a - * Sequencer along with the relationships between the various animations. The - * intention of the Builder methods, along with the {@link - * Sequencer#play(Animatable) play()} method of Sequencer is to make it possible to - * express the dependency relationships of animations in a natural way. Developers can also use - * the {@link Sequencer#playTogether(Animatable[]) playTogether()} and {@link - * Sequencer#playSequentially(Animatable[]) playSequentially()} methods if these suit the need, - * but it might be easier in some situations to express the sequence of animations in pairs. - *

- *

The Builder object cannot be constructed directly, but is rather constructed - * internally via a call to {@link Sequencer#play(Animatable)}.

- *

- *

For example, this sets up a Sequencer to play anim1 and anim2 at the same time, anim3 to - * play when anim2 finishes, and anim4 to play when anim3 finishes:

- *
-     *     Sequencer s = new Sequencer();
-     *     s.play(anim1).with(anim2);
-     *     s.play(anim2).before(anim3);
-     *     s.play(anim4).after(anim3);
-     * 
- *

- *

Note in the example that both {@link Builder#before(Animatable)} and {@link - * Builder#after(Animatable)} are used. These are just different ways of expressing the same - * relationship and are provided to make it easier to say things in a way that is more natural, - * depending on the situation.

- *

- *

It is possible to make several calls into the same Builder object to express - * multiple relationships. However, note that it is only the animation passed into the initial - * {@link Sequencer#play(Animatable)} method that is the dependency in any of the successive - * calls to the Builder object. For example, the following code starts both anim2 - * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and - * anim3: - *

-     *   Sequencer s = new Sequencer();
-     *   s.play(anim1).before(anim2).before(anim3);
-     * 
- * If the desired result is to play anim1 then anim2 then anim3, this code expresses the - * relationship correctly:

- *
-     *   Sequencer s = new Sequencer();
-     *   s.play(anim1).before(anim2);
-     *   s.play(anim2).before(anim3);
-     * 
- *

- *

Note that it is possible to express relationships that cannot be resolved and will not - * result in sensible results. For example, play(anim1).after(anim1) makes no - * sense. In general, circular dependencies like this one (or more indirect ones where a depends - * on b, which depends on c, which depends on a) should be avoided. Only create sequences that - * can boil down to a simple, one-way relationship of animations starting with, before, and - * after other, different, animations.

- */ - public class Builder { - - /** - * This tracks the current node being processed. It is supplied to the play() method - * of Sequencer and passed into the constructor of Builder. - */ - private Node mCurrentNode; - - /** - * package-private constructor. Builders are only constructed by Sequencer, when the - * play() method is called. - * - * @param anim The animation that is the dependency for the other animations passed into - * the other methods of this Builder object. - */ - Builder(Animatable anim) { - mCurrentNode = mNodeMap.get(anim); - if (mCurrentNode == null) { - mCurrentNode = new Node(anim); - mNodeMap.put(anim, mCurrentNode); - mNodes.add(mCurrentNode); - } - } - - /** - * Sets up the given animation to play at the same time as the animation supplied in the - * {@link Sequencer#play(Animatable)} call that created this Builder object. - * - * @param anim The animation that will play when the animation supplied to the - * {@link Sequencer#play(Animatable)} method starts. - */ - public void with(Animatable anim) { - Node node = mNodeMap.get(anim); - if (node == null) { - node = new Node(anim); - mNodeMap.put(anim, node); - mNodes.add(node); - } - Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH); - node.addDependency(dependency); - } - - /** - * Sets up the given animation to play when the animation supplied in the - * {@link Sequencer#play(Animatable)} call that created this Builder object - * ends. - * - * @param anim The animation that will play when the animation supplied to the - * {@link Sequencer#play(Animatable)} method ends. - */ - public void before(Animatable anim) { - Node node = mNodeMap.get(anim); - if (node == null) { - node = new Node(anim); - mNodeMap.put(anim, node); - mNodes.add(node); - } - Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER); - node.addDependency(dependency); - } - - /** - * Sets up the given animation to play when the animation supplied in the - * {@link Sequencer#play(Animatable)} call that created this Builder object - * to start when the animation supplied in this method call ends. - * - * @param anim The animation whose end will cause the animation supplied to the - * {@link Sequencer#play(Animatable)} method to play. - */ - public void after(Animatable anim) { - Node node = mNodeMap.get(anim); - if (node == null) { - node = new Node(anim); - mNodeMap.put(anim, node); - mNodes.add(node); - } - Dependency dependency = new Dependency(node, Dependency.AFTER); - mCurrentNode.addDependency(dependency); - } - - /** - * Sets up the animation supplied in the - * {@link Sequencer#play(Animatable)} call that created this Builder object - * to play when the given amount of time elapses. - * - * @param delay The number of milliseconds that should elapse before the - * animation starts. - */ - public void after(long delay) { - // setup dummy Animator just to run the clock - after(new Animator(delay, 0f, 1f)); - } - - } - -} diff --git a/core/java/android/animation/TypeEvaluator.java b/core/java/android/animation/TypeEvaluator.java index 6150e00..fa49175 100644 --- a/core/java/android/animation/TypeEvaluator.java +++ b/core/java/android/animation/TypeEvaluator.java @@ -17,12 +17,12 @@ package android.animation; /** - * Interface for use with the {@link Animator#setEvaluator(TypeEvaluator)} function. Evaluators + * Interface for use with the {@link ValueAnimator#setEvaluator(TypeEvaluator)} function. Evaluators * allow developers to create animations on arbitrary property types, by allowing them to supply * custom evaulators for types that are not automatically understood and used by the animation * system. * - * @see Animator#setEvaluator(TypeEvaluator) + * @see ValueAnimator#setEvaluator(TypeEvaluator) */ public interface TypeEvaluator { diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java new file mode 100755 index 0000000..54a8e4b --- /dev/null +++ b/core/java/android/animation/ValueAnimator.java @@ -0,0 +1,972 @@ +/* + * Copyright (C) 2010 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 android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * This class provides a simple timing engine for running animations + * which calculate animated values and set them on target objects. + * + *

There is a single timing pulse that all animations use. It runs in a + * custom handler to ensure that property changes happen on the UI thread.

+ * + *

By default, ValueAnimator uses non-linear time interpolation, via the + * {@link AccelerateDecelerateInterpolator} class, which accelerates into and decelerates + * out of an animation. This behavior can be changed by calling + * {@link ValueAnimator#setInterpolator(Interpolator)}.

+ */ +public class ValueAnimator extends Animator { + + /** + * Internal constants + */ + + /* + * The default amount of time in ms between animation frames + */ + private static final long DEFAULT_FRAME_DELAY = 30; + + /** + * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent + * by the handler to itself to process the next animation frame + */ + private static final int ANIMATION_START = 0; + private static final int ANIMATION_FRAME = 1; + + /** + * Values used with internal variable mPlayingState to indicate the current state of an + * animation. + */ + 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 + + /** + * Internal variables + * NOTE: This object implements the clone() method, making a deep copy of any referenced + * objects. As other non-trivial fields are added to this class, make sure to add logic + * to clone() to make deep copies of them. + */ + + // The first time that the animation's animateFrame() method is called. This time is used to + // determine elapsed time (and therefore the elapsed fraction) in subsequent calls + // to animateFrame() + private long mStartTime; + + /** + * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked + * to a value. + */ + private long mSeekTime = -1; + + // The static sAnimationHandler processes the internal timing loop on which all animations + // are based + private static AnimationHandler sAnimationHandler; + + // The static list of all active animations + private static final ArrayList sAnimations = new ArrayList(); + + // The set of animations to be started on the next animation frame + private static final ArrayList sPendingAnimations = new ArrayList(); + + // The time interpolator to be used if none is set on the animation + private static final Interpolator sDefaultInterpolator = new AccelerateDecelerateInterpolator(); + + // type evaluators for the three primitive types handled by this implementation + private static final TypeEvaluator sIntEvaluator = new IntEvaluator(); + private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator(); + private static final TypeEvaluator sDoubleEvaluator = new DoubleEvaluator(); + + /** + * Used to indicate whether the animation is currently playing in reverse. This causes the + * elapsed fraction to be inverted to calculate the appropriate values. + */ + private boolean mPlayingBackwards = false; + + /** + * This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the + * repeatCount (if repeatCount!=INFINITE), the animation ends + */ + private int mCurrentIteration = 0; + + /** + * Tracks whether a startDelay'd animation has begun playing through the startDelay. + */ + private boolean mStartedDelay = false; + + /** + * Tracks the time at which the animation began playing through its startDelay. This is + * different from the mStartTime variable, which is used to track when the animation became + * active (which is when the startDelay expired and the animation was added to the active + * animations list). + */ + private long mDelayStartTime; + + /** + * 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. + */ + private int mPlayingState = STOPPED; + + /** + * Internal collections used to avoid set collisions as animations start and end while being + * processed. + */ + private static final ArrayList sEndingAnims = new ArrayList(); + private static final ArrayList sDelayedAnims = new ArrayList(); + private static final ArrayList sReadyAnims = new ArrayList(); + + /** + * Flag that denotes whether the animation is set up and ready to go. Used to + * set up animation that has not yet been started. + */ + boolean mInitialized = false; + + // + // Backing variables + // + + // How long the animation should last in ms + private long mDuration; + + // The amount of time in ms to delay starting the animation after start() is called + private long mStartDelay = 0; + + // The number of milliseconds between animation frames + private static long sFrameDelay = DEFAULT_FRAME_DELAY; + + // The number of times the animation will repeat. The default is 0, which means the animation + // will play only once + private int mRepeatCount = 0; + + /** + * The type of repetition that will occur when repeatMode is nonzero. RESTART means the + * animation will start from the beginning on every new cycle. REVERSE means the animation + * will reverse directions on each iteration. + */ + private int mRepeatMode = RESTART; + + /** + * The time interpolator to be used. The elapsed fraction of the animation will be passed + * through this interpolator to calculate the interpolated fraction, which is then used to + * calculate the animated values. + */ + private Interpolator mInterpolator = sDefaultInterpolator; + + /** + * The set of listeners to be sent events through the life of an animation. + */ + private ArrayList mUpdateListeners = null; + + /** + * The property/value sets being animated. + */ + PropertyValuesHolder[] mValues; + + /** + * A hashmap of the PropertyValuesHolder objects. This map is used to lookup animated values + * by property name during calls to getAnimatedValue(String). + */ + HashMap mValuesMap; + + /** + * Public constants + */ + + /** + * When the animation reaches the end and repeatCount is INFINITE + * or a positive value, the animation restarts from the beginning. + */ + public static final int RESTART = 1; + /** + * When the animation reaches the end and repeatCount is INFINITE + * or a positive value, the animation reverses direction on every iteration. + */ + public static final int REVERSE = 2; + /** + * This value used used with the {@link #setRepeatCount(int)} property to repeat + * the animation indefinitely. + */ + public static final int INFINITE = -1; + + /** + * Creates a new ValueAnimator object. This default constructor is primarily for + * use internally; the other constructors which take parameters are more generally + * useful. + */ + public ValueAnimator() { + } + + /** + * Constructs an ValueAnimator object with the specified duration and set of + * values. If the values are a set of PropertyValuesHolder objects, then these objects + * define the potentially multiple properties being animated and the values the properties are + * animated between. Otherwise, the values define a single set of values animated between. + * + * @param duration The length of the animation, in milliseconds. + * @param values The set of values to animate between. If these values are not + * PropertyValuesHolder objects, then there should be more than one value, since the values + * determine the interval to animate between. + */ + public ValueAnimator(long duration, T...values) { + mDuration = duration; + if (values.length > 0) { + setValues(values); + } + } + + /** + * Sets the values, per property, being animated between. This function is called internally + * by the constructors of ValueAnimator that take a list of values. But an ValueAnimator can + * be constructed without values and this method can be called to set the values manually + * instead. + * + * @param values The set of values, per property, being animated between. + */ + public void setValues(PropertyValuesHolder... values) { + int numValues = values.length; + mValues = values; + mValuesMap = new HashMap(numValues); + for (int i = 0; i < numValues; ++i) { + PropertyValuesHolder valuesHolder = (PropertyValuesHolder) values[i]; + mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); + } + } + + /** + * Returns the values that this ValueAnimator animates between. These values are stored in + * PropertyValuesHolder objects, even if the ValueAnimator was created with a simple list + * of value objects instead. + * + * @return PropertyValuesHolder[] An array of PropertyValuesHolder objects which hold the + * values, per property, that define the animation. + */ + public PropertyValuesHolder[] getValues() { + return mValues; + } + + /** + * Sets the values to animate between for this animation. If values is + * a set of PropertyValuesHolder objects, these objects will become the set of properties + * animated and the values that those properties are animated between. Otherwise, this method + * will set only one set of values for the ValueAnimator. Also, if the values are not + * PropertyValuesHolder objects and if there are already multiple sets of + * values defined for this ValueAnimator via + * more than one PropertyValuesHolder objects, this method will set the values for + * the first of those objects. + * + * @param values The set of values to animate between. + */ + public void setValues(T... values) { + if (mValues == null || mValues.length == 0) { + setValues(new PropertyValuesHolder[]{ + new PropertyValuesHolder("", (Object[])values)}); + } else { + PropertyValuesHolder valuesHolder = mValues[0]; + valuesHolder.setValues(values); + } + } + + /** + * This function is called immediately before processing the first animation + * frame of an animation. If there is a nonzero startDelay, the + * function is called after that delay ends. + * It takes care of the final initialization steps for the + * animation. + * + *

Overrides of this method should call the superclass method to ensure + * that internal mechanisms for the animation are set up correctly.

+ */ + void initAnimation() { + if (!mInitialized) { + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].init(); + } + mCurrentIteration = 0; + mInitialized = true; + } + } + + + /** + * Sets the length of the animation. + * + * @param duration The length of the animation, in milliseconds. + */ + public void setDuration(long duration) { + mDuration = duration; + } + + /** + * Gets the length of the animation. + * + * @return The length of the animation, in milliseconds. + */ + public long getDuration() { + return mDuration; + } + + /** + * Sets the position of the animation to the specified point in time. This time should + * be between 0 and the total duration of the animation, including any repetition. If + * the animation has not yet been started, then it will not advance forward after it is + * set to this time; it will simply set the time to this value and perform any appropriate + * actions based on that time. If the animation is already running, then setCurrentPlayTime() + * will set the current playing time to this value and continue playing from that point. + * + * @param playTime The time, in milliseconds, to which the animation is advanced or rewound. + */ + public void setCurrentPlayTime(long playTime) { + initAnimation(); + long currentTime = AnimationUtils.currentAnimationTimeMillis(); + if (mPlayingState != RUNNING) { + mSeekTime = playTime; + mPlayingState = SEEKED; + } + mStartTime = currentTime - playTime; + animationFrame(currentTime); + } + + /** + * Gets the current position of the animation in time, which is equal to the current + * time minus the time that the animation started. An animation that is not yet started will + * return a value of zero. + * + * @return The current position in time of the animation. + */ + public long getCurrentPlayTime() { + if (!mInitialized || mPlayingState == STOPPED) { + return 0; + } + return AnimationUtils.currentAnimationTimeMillis() - mStartTime; + } + + /** + * This custom, static handler handles the timing pulse that is shared by + * all active animations. This approach ensures that the setting of animation + * values will happen on the UI thread and that all animations will share + * the same times for calculating their values, which makes synchronizing + * animations possible. + * + */ + private static class AnimationHandler extends Handler { + /** + * There are only two messages that we care about: ANIMATION_START and + * ANIMATION_FRAME. The START message is sent when an animation's start() + * method is called. It cannot start synchronously when start() is called + * because the call may be on the wrong thread, and it would also not be + * synchronized with other animations because it would not start on a common + * timing pulse. So each animation sends a START message to the handler, which + * causes the handler to place the animation on the active animations queue and + * start processing frames for that animation. + * The FRAME message is the one that is sent over and over while there are any + * active animations to process. + */ + @Override + public void handleMessage(Message msg) { + boolean callAgain = true; + switch (msg.what) { + // TODO: should we avoid sending frame message when starting if we + // were already running? + case ANIMATION_START: + if (sAnimations.size() > 0 || sDelayedAnims.size() > 0) { + callAgain = false; + } + // pendingAnims holds any animations that have requested to be started + // We're going to clear sPendingAnimations, but starting animation may + // cause more to be added to the pending list (for example, if one animation + // starting triggers another starting). So we loop until sPendingAnimations + // is empty. + while (sPendingAnimations.size() > 0) { + ArrayList pendingCopy = + (ArrayList) sPendingAnimations.clone(); + sPendingAnimations.clear(); + int count = pendingCopy.size(); + 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) { + anim.startAnimation(); + } else { + sDelayedAnims.add(anim); + } + } + } + // fall through to process first frame of new animations + case ANIMATION_FRAME: + // currentTime holds the common time for all animations processed + // during this frame + long currentTime = AnimationUtils.currentAnimationTimeMillis(); + + // First, process animations currently sitting on the delayed queue, adding + // them to the active animations if they are ready + int numDelayedAnims = sDelayedAnims.size(); + for (int i = 0; i < numDelayedAnims; ++i) { + ValueAnimator anim = sDelayedAnims.get(i); + if (anim.delayedAnimationFrame(currentTime)) { + sReadyAnims.add(anim); + } + } + int numReadyAnims = sReadyAnims.size(); + if (numReadyAnims > 0) { + for (int i = 0; i < numReadyAnims; ++i) { + ValueAnimator anim = sReadyAnims.get(i); + anim.startAnimation(); + sDelayedAnims.remove(anim); + } + sReadyAnims.clear(); + } + + // Now process all active animations. The return value from animationFrame() + // tells the handler whether it should now be ended + int numAnims = sAnimations.size(); + for (int i = 0; i < numAnims; ++i) { + ValueAnimator anim = sAnimations.get(i); + if (anim.animationFrame(currentTime)) { + sEndingAnims.add(anim); + } + } + if (sEndingAnims.size() > 0) { + for (int i = 0; i < sEndingAnims.size(); ++i) { + sEndingAnims.get(i).endAnimation(); + } + sEndingAnims.clear(); + } + + // If there are still active or delayed animations, call the handler again + // after the frameDelay + if (callAgain && (!sAnimations.isEmpty() || !sDelayedAnims.isEmpty())) { + sendEmptyMessageDelayed(ANIMATION_FRAME, sFrameDelay); + } + break; + } + } + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + * + * @return the number of milliseconds to delay running the animation + */ + public long getStartDelay() { + return mStartDelay; + } + + /** + * The amount of time, in milliseconds, to delay starting the animation after + * {@link #start()} is called. + + * @param startDelay The amount of the delay, in milliseconds + */ + public void setStartDelay(long startDelay) { + this.mStartDelay = startDelay; + } + + /** + * The amount of time, in milliseconds, between each frame of the animation. This is a + * requested time that the animation will attempt to honor, but the actual delay between + * frames may be different, depending on system load and capabilities. This is a static + * function because the same delay will be applied to all animations, since they are all + * run off of a single timing loop. + * + * @return the requested time between frames, in milliseconds + */ + public static long getFrameDelay() { + return sFrameDelay; + } + + /** + * The amount of time, in milliseconds, between each frame of the animation. This is a + * requested time that the animation will attempt to honor, but the actual delay between + * frames may be different, depending on system load and capabilities. This is a static + * function because the same delay will be applied to all animations, since they are all + * run off of a single timing loop. + * + * @param frameDelay the requested time between frames, in milliseconds + */ + public static void setFrameDelay(long frameDelay) { + sFrameDelay = frameDelay; + } + + /** + * The most recent value calculated by this ValueAnimator when there is just one + * property being animated. This value is only sensible while the animation is running. The main + * purpose for this read-only property is to retrieve the value from the ValueAnimator + * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which + * is called during each animation frame, immediately after the value is calculated. + * + * @return animatedValue The value most recently calculated by this ValueAnimator for + * the single property being animated. If there are several properties being animated + * (specified by several PropertyValuesHolder objects in the constructor), this function + * returns the animated value for the first of those objects. + */ + public Object getAnimatedValue() { + if (mValues != null && mValues.length > 0) { + return mValues[0].getAnimatedValue(); + } + // Shouldn't get here; should always have values unless ValueAnimator was set up wrong + return null; + } + + /** + * The most recent value calculated by this ValueAnimator for propertyName. + * The main purpose for this read-only property is to retrieve the value from the + * ValueAnimator during a call to + * {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which + * is called during each animation frame, immediately after the value is calculated. + * + * @return animatedValue The value most recently calculated for the named property + * by this ValueAnimator. + */ + public Object getAnimatedValue(String propertyName) { + PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName); + if (valuesHolder != null) { + return valuesHolder.getAnimatedValue(); + } else { + // At least avoid crashing if called with bogus propertyName + return null; + } + } + + /** + * Sets how many times the animation should be repeated. If the repeat + * count is 0, the animation is never repeated. If the repeat count is + * greater than 0 or {@link #INFINITE}, the repeat mode will be taken + * into account. The repeat count is 0 by default. + * + * @param value the number of times the animation should be repeated + */ + public void setRepeatCount(int value) { + mRepeatCount = value; + } + /** + * Defines how many times the animation should repeat. The default value + * is 0. + * + * @return the number of times the animation should repeat, or {@link #INFINITE} + */ + public int getRepeatCount() { + return mRepeatCount; + } + + /** + * Defines what this animation should do when it reaches the end. This + * setting is applied only when the repeat count is either greater than + * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}. + * + * @param value {@link #RESTART} or {@link #REVERSE} + */ + public void setRepeatMode(int value) { + mRepeatMode = value; + } + + /** + * Defines what this animation should do when it reaches the end. + * + * @return either one of {@link #REVERSE} or {@link #RESTART} + */ + public int getRepeatMode() { + return mRepeatMode; + } + + /** + * Adds a listener to the set of listeners that are sent update events through the life of + * an animation. This method is called on all listeners for every frame of the animation, + * after the values for the animation have been calculated. + * + * @param listener the listener to be added to the current set of listeners for this animation. + */ + public void addUpdateListener(AnimatorUpdateListener listener) { + if (mUpdateListeners == null) { + mUpdateListeners = new ArrayList(); + } + mUpdateListeners.add(listener); + } + + /** + * Removes a listener from the set listening to frame updates for this animation. + * + * @param listener the listener to be removed from the current set of update listeners + * for this animation. + */ + public void removeUpdateListener(AnimatorUpdateListener listener) { + if (mUpdateListeners == null) { + return; + } + mUpdateListeners.remove(listener); + if (mUpdateListeners.size() == 0) { + mUpdateListeners = null; + } + } + + + /** + * The time interpolator used in calculating the elapsed fraction of this animation. The + * interpolator determines whether the animation runs with linear or non-linear motion, + * such as acceleration and deceleration. The default value is + * {@link android.view.animation.AccelerateDecelerateInterpolator} + * + * @param value the interpolator to be used by this animation + */ + @Override + public void setInterpolator(Interpolator value) { + if (value != null) { + mInterpolator = value; + } + } + + /** + * Returns the timing interpolator that this ValueAnimator uses. + * + * @return The timing interpolator for this ValueAnimator. + */ + public Interpolator getInterpolator() { + return mInterpolator; + } + + /** + * The type evaluator to be used when calculating the animated values of this animation. + * The system will automatically assign a float, int, or double evaluator based on the type + * of startValue and endValue in the constructor. But if these values + * are not one of these primitive types, or if different evaluation is desired (such as is + * necessary with int values that represent colors), a custom evaluator needs to be assigned. + * For example, when running an animation on color values, the {@link RGBEvaluator} + * should be used to get correct RGB color interpolation. + * + *

If this ValueAnimator has only one set of values being animated between, this evaluator + * will be used for that set. If there are several sets of values being animated, which is + * the case if PropertyValuesHOlder objects were set on the ValueAnimator, then the evaluator + * is assigned just to the first PropertyValuesHolder object.

+ * + * @param value the evaluator to be used this animation + */ + public void setEvaluator(TypeEvaluator value) { + if (value != null && mValues != null && mValues.length > 0) { + mValues[0].setEvaluator(value); + } + } + + /** + * Start the animation playing. This version of start() takes a boolean flag that indicates + * whether the animation should play in reverse. The flag is usually false, but may be set + * to true if called from the reverse() method/ + * + * @param playBackwards Whether the ValueAnimator should start playing in reverse. + */ + private void start(boolean playBackwards) { + mPlayingBackwards = playBackwards; + if ((mStartDelay == 0) && (Thread.currentThread() == Looper.getMainLooper().getThread())) { + // This sets the initial value of the animation, prior to actually starting it running + setCurrentPlayTime(getCurrentPlayTime()); + } + mPlayingState = STOPPED; + mStartedDelay = false; + sPendingAnimations.add(this); + if (sAnimationHandler == null) { + sAnimationHandler = new AnimationHandler(); + } + // TODO: does this put too many messages on the queue if the handler + // is already running? + sAnimationHandler.sendEmptyMessage(ANIMATION_START); + } + + @Override + public void start() { + start(false); + } + + @Override + public void cancel() { + if (mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationCancel(this); + } + } + // Just set the CANCELED flag - this causes the animation to end the next time a frame + // is processed. + mPlayingState = CANCELED; + } + + @Override + public void end() { + if (!sAnimations.contains(this) && !sPendingAnimations.contains(this)) { + // Special case if the animation has not yet started; get it ready for ending + mStartedDelay = false; + sPendingAnimations.add(this); + if (sAnimationHandler == null) { + sAnimationHandler = new AnimationHandler(); + } + sAnimationHandler.sendEmptyMessage(ANIMATION_START); + } + // Just set the ENDED flag - this causes the animation to end the next time a frame + // is processed. + mPlayingState = ENDED; + } + + @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); + } + + /** + * Plays the ValueAnimator in reverse. If the animation is already running, + * it will stop itself and play backwards from the point reached when reverse was called. + * If the animation is not currently running, then it will start from the end and + * play backwards. This behavior is only set for the current animation; future playing + * of the animation will use the default behavior of playing forward. + */ + public void reverse() { + mPlayingBackwards = !mPlayingBackwards; + if (mPlayingState == RUNNING) { + long currentTime = AnimationUtils.currentAnimationTimeMillis(); + long currentPlayTime = currentTime - mStartTime; + long timeLeft = mDuration - currentPlayTime; + mStartTime = currentTime - timeLeft; + } else { + start(true); + } + } + + /** + * Called internally to end an animation by removing it from the animations list. Must be + * called on the UI thread. + */ + private void endAnimation() { + sAnimations.remove(this); + mPlayingState = STOPPED; + if (mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationEnd(this); + } + } + } + + /** + * Called internally to start an animation by adding it to the active animations list. Must be + * called on the UI thread. + */ + private void startAnimation() { + initAnimation(); + sAnimations.add(this); + if (mListeners != null) { + ArrayList tmpListeners = + (ArrayList) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationStart(this); + } + } + } + + /** + * Internal function called to process an animation frame on an animation that is currently + * sleeping through its startDelay phase. The return value indicates whether it + * should be woken up and put on the active animations queue. + * + * @param currentTime The current animation time, used to calculate whether the animation + * has exceeded its startDelay and should be started. + * @return True if the animation's startDelay has been exceeded and the animation + * 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; + } else { + long deltaTime = currentTime - mDelayStartTime; + if (deltaTime > mStartDelay) { + // startDelay ended - start the anim and record the + // mStartTime appropriately + mStartTime = currentTime - (deltaTime - mStartDelay); + mPlayingState = RUNNING; + return true; + } + } + return false; + } + + /** + * This internal function processes a single animation frame for a given animation. The + * currentTime parameter is the timing pulse sent by the handler, used to calculate the + * elapsed duration, and therefore + * the elapsed fraction, of the animation. The return value indicates whether the animation + * should be ended (which happens when the elapsed time of the animation exceeds the + * animation's duration, including the repeatCount). + * + * @param currentTime The current time, as tracked by the static timing handler + * @return true if the animation's duration, including any repetitions due to + * repeatCount has been exceeded and the animation should be ended. + */ + private boolean animationFrame(long currentTime) { + boolean done = false; + + if (mPlayingState == STOPPED) { + mPlayingState = RUNNING; + if (mSeekTime < 0) { + mStartTime = currentTime; + } else { + mStartTime = currentTime - mSeekTime; + // Now that we're playing, reset the seek time + mSeekTime = -1; + } + } + switch (mPlayingState) { + case RUNNING: + case SEEKED: + float fraction = (float)(currentTime - mStartTime) / mDuration; + if (fraction >= 1f) { + if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) { + // Time to repeat + if (mListeners != null) { + for (AnimatorListener listener : mListeners) { + listener.onAnimationRepeat(this); + } + } + ++mCurrentIteration; + if (mRepeatMode == REVERSE) { + mPlayingBackwards = mPlayingBackwards ? false : true; + } + // TODO: doesn't account for fraction going Wayyyyy over 1, like 2+ + fraction = fraction - 1f; + mStartTime += mDuration; + } else { + done = true; + fraction = Math.min(fraction, 1.0f); + } + } + if (mPlayingBackwards) { + fraction = 1f - fraction; + } + 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; + } + + /** + * This method is called with the elapsed fraction of the animation during every + * animation frame. This function turns the elapsed fraction into an interpolated fraction + * and then into an animated value (from the evaluator. The function is called mostly during + * animation updates, but it is also called when the end() + * function is called, to set the final value on the property. + * + *

Overrides of this method must call the superclass to perform the calculation + * of the animated value.

+ * + * @param fraction The elapsed fraction of the animation. + */ + void animateValue(float fraction) { + fraction = mInterpolator.getInterpolation(fraction); + int numValues = mValues.length; + 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); + } + } + } + + @Override + public ValueAnimator clone() { + final ValueAnimator anim = (ValueAnimator) super.clone(); + if (mUpdateListeners != null) { + ArrayList oldListeners = mUpdateListeners; + anim.mUpdateListeners = new ArrayList(); + int numListeners = oldListeners.size(); + for (int i = 0; i < numListeners; ++i) { + anim.mUpdateListeners.add(oldListeners.get(i)); + } + } + anim.mSeekTime = -1; + anim.mPlayingBackwards = false; + anim.mCurrentIteration = 0; + anim.mInitialized = false; + anim.mPlayingState = STOPPED; + anim.mStartedDelay = false; + PropertyValuesHolder[] oldValues = mValues; + if (oldValues != null) { + int numValues = oldValues.length; + anim.mValues = new PropertyValuesHolder[numValues]; + for (int i = 0; i < numValues; ++i) { + anim.mValues[i] = oldValues[i].clone(); + } + anim.mValuesMap = new HashMap(numValues); + for (int i = 0; i < numValues; ++i) { + PropertyValuesHolder valuesHolder = mValues[i]; + anim.mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); + } + } + return anim; + } + + /** + * Implementors of this interface can add themselves as update listeners + * to an ValueAnimator instance to receive callbacks on every animation + * frame, after the current frame's values have been calculated for that + * ValueAnimator. + */ + public static interface AnimatorUpdateListener { + /** + *

Notifies the occurrence of another frame of the animation.

+ * + * @param animation The animation which was repeated. + */ + void onAnimationUpdate(ValueAnimator animation); + + } +} \ No newline at end of file diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 56cf399..16d105a 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -16,7 +16,7 @@ package android.app; -import android.animation.Animatable; +import android.animation.Animator; import android.content.ComponentCallbacks; import android.content.Context; import android.content.Intent; @@ -26,7 +26,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.AndroidRuntimeException; import android.util.AttributeSet; -import android.util.Log; import android.util.SparseArray; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -755,7 +754,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener /** * Called when a fragment loads an animation. */ - public Animatable onCreateAnimatable(int transit, boolean enter, int nextAnim) { + public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) { return null; } diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 7641f61..4d4f892 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -16,10 +16,10 @@ package android.app; -import android.animation.Animatable; -import android.animation.AnimatableInflater; -import android.animation.PropertyAnimator; -import android.animation.Sequencer; +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.content.res.TypedArray; import android.os.Bundle; import android.os.Handler; @@ -248,16 +248,16 @@ final class FragmentManagerImpl implements FragmentManager { return f; } - Animatable loadAnimatable(Fragment fragment, int transit, boolean enter, + Animator loadAnimator(Fragment fragment, int transit, boolean enter, int transitionStyle) { - Animatable animObj = fragment.onCreateAnimatable(transit, enter, + Animator animObj = fragment.onCreateAnimator(transit, enter, fragment.mNextAnim); if (animObj != null) { return animObj; } if (fragment.mNextAnim != 0) { - Animatable anim = AnimatableInflater.loadAnimatable(mActivity, fragment.mNextAnim); + Animator anim = AnimatorInflater.loadAnimator(mActivity, fragment.mNextAnim); if (anim != null) { return anim; } @@ -288,7 +288,7 @@ final class FragmentManagerImpl implements FragmentManager { return null; } - return AnimatableInflater.loadAnimatable(mActivity, anim); + return AnimatorInflater.loadAnimator(mActivity, anim); } void moveToState(Fragment f, int newState, int transit, int transitionStyle) { @@ -360,13 +360,13 @@ final class FragmentManagerImpl implements FragmentManager { if (f.mView != null) { f.mView.setSaveFromParentEnabled(false); if (container != null) { - Animatable anim = loadAnimatable(f, transit, true, + Animator anim = loadAnimator(f, transit, true, transitionStyle); if (anim != null) { - if (anim instanceof Sequencer) { - ((Sequencer)anim).setTarget(f.mView); - } else if (anim instanceof PropertyAnimator) { - ((PropertyAnimator)anim).setTarget(f.mView); + if (anim instanceof AnimatorSet) { + ((AnimatorSet)anim).setTarget(f.mView); + } else if (anim instanceof ObjectAnimator) { + ((ObjectAnimator)anim).setTarget(f.mView); } anim.start(); } @@ -448,13 +448,13 @@ final class FragmentManagerImpl implements FragmentManager { } if (f.mView != null && f.mContainer != null) { if (mCurState > Fragment.INITIALIZING) { - Animatable anim = loadAnimatable(f, transit, true, + Animator anim = loadAnimator(f, transit, true, transitionStyle); if (anim != null) { - if (anim instanceof Sequencer) { - ((Sequencer)anim).setTarget(f.mView); - } else if (anim instanceof PropertyAnimator) { - ((PropertyAnimator)anim).setTarget(f.mView); + if (anim instanceof AnimatorSet) { + ((AnimatorSet)anim).setTarget(f.mView); + } else if (anim instanceof ObjectAnimator) { + ((ObjectAnimator)anim).setTarget(f.mView); } anim.start(); } @@ -588,13 +588,13 @@ final class FragmentManagerImpl implements FragmentManager { if (!fragment.mHidden) { fragment.mHidden = true; if (fragment.mView != null) { - Animatable anim = loadAnimatable(fragment, transition, true, + Animator anim = loadAnimator(fragment, transition, true, transitionStyle); if (anim != null) { - if (anim instanceof Sequencer) { - ((Sequencer)anim).setTarget(fragment.mView); - } else if (anim instanceof PropertyAnimator) { - ((PropertyAnimator)anim).setTarget(fragment.mView); + if (anim instanceof AnimatorSet) { + ((AnimatorSet)anim).setTarget(fragment.mView); + } else if (anim instanceof ObjectAnimator) { + ((ObjectAnimator)anim).setTarget(fragment.mView); } anim.start(); } @@ -612,13 +612,13 @@ final class FragmentManagerImpl implements FragmentManager { if (fragment.mHidden) { fragment.mHidden = false; if (fragment.mView != null) { - Animatable anim = loadAnimatable(fragment, transition, true, + Animator anim = loadAnimator(fragment, transition, true, transitionStyle); if (anim != null) { - if (anim instanceof Sequencer) { - ((Sequencer)anim).setTarget(fragment.mView); - } else if (anim instanceof PropertyAnimator) { - ((PropertyAnimator)anim).setTarget(fragment.mView); + if (anim instanceof AnimatorSet) { + ((AnimatorSet)anim).setTarget(fragment.mView); + } else if (anim instanceof ObjectAnimator) { + ((ObjectAnimator)anim).setTarget(fragment.mView); } anim.start(); } diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index c090f8e..32ff647 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -35,7 +35,7 @@ import java.io.IOException; public class AnimationUtils { /** - * These flags are used when parsing Sequencer objects + * These flags are used when parsing AnimatorSet objects */ private static final int TOGETHER = 0; private static final int SEQUENTIALLY = 1; diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java index 830e899..e8d96c5 100644 --- a/core/java/android/widget/AdapterViewAnimator.java +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -18,12 +18,10 @@ package android.widget; import java.util.ArrayList; -import android.animation.PropertyAnimator; +import android.animation.ObjectAnimator; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; -import android.graphics.Rect; -import android.graphics.RectF; import android.os.Handler; import android.os.Looper; import android.os.Parcel; @@ -220,13 +218,13 @@ public abstract class AdapterViewAnimator extends AdapterView * @param view The view that is being animated */ void animateViewForTransition(int fromIndex, int toIndex, View view) { - PropertyAnimator pa; + ObjectAnimator pa; if (fromIndex == -1) { view.setAlpha(0.0f); - pa = new PropertyAnimator(400, view, "alpha", 0.0f, 1.0f); + pa = new ObjectAnimator(400, view, "alpha", 0.0f, 1.0f); pa.start(); } else if (toIndex == -1) { - pa = new PropertyAnimator(400, view, "alpha", 1.0f, 0.0f); + pa = new ObjectAnimator(400, view, "alpha", 1.0f, 0.0f); pa.start(); } } diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java index 9025b83..0a97904 100644 --- a/core/java/android/widget/StackView.java +++ b/core/java/android/widget/StackView.java @@ -16,8 +16,8 @@ package android.widget; -import android.animation.PropertyAnimator; import android.animation.PropertyValuesHolder; +import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BlurMaskFilter; @@ -166,7 +166,7 @@ public class StackView extends AdapterViewAnimator { } view.setVisibility(VISIBLE); - PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, + ObjectAnimator fadeIn = new ObjectAnimator(DEFAULT_ANIMATION_DURATION, view, "alpha", view.getAlpha(), 1.0f); fadeIn.start(); } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) { @@ -181,7 +181,7 @@ public class StackView extends AdapterViewAnimator { new PropertyValuesHolder("YProgress", 0.0f); PropertyValuesHolder slideInX = new PropertyValuesHolder("XProgress", 0.0f); - PropertyAnimator pa = new PropertyAnimator(duration, animationSlider, + ObjectAnimator pa = new ObjectAnimator(duration, animationSlider, slideInX, slideInY); pa.setInterpolator(new LinearInterpolator()); pa.start(); @@ -196,7 +196,7 @@ public class StackView extends AdapterViewAnimator { new PropertyValuesHolder("YProgress", 1.0f); PropertyValuesHolder slideOutX = new PropertyValuesHolder("XProgress", 0.0f); - PropertyAnimator pa = new PropertyAnimator(duration, animationSlider, + ObjectAnimator pa = new ObjectAnimator(duration, animationSlider, slideOutX, slideOutY); pa.setInterpolator(new LinearInterpolator()); pa.start(); @@ -208,7 +208,7 @@ public class StackView extends AdapterViewAnimator { lp.setVerticalOffset(-mViewHeight); } else if (toIndex == -1) { // Fade item out - PropertyAnimator fadeOut = new PropertyAnimator + ObjectAnimator fadeOut = new ObjectAnimator (DEFAULT_ANIMATION_DURATION, view, "alpha", view.getAlpha(), 0.0f); fadeOut.start(); } @@ -234,7 +234,7 @@ public class StackView extends AdapterViewAnimator { PropertyValuesHolder translationY = new PropertyValuesHolder("translationY", transY); - PropertyAnimator pa = new PropertyAnimator(100, view, scaleX, scaleY, translationY); + ObjectAnimator pa = new ObjectAnimator(100, view, scaleX, scaleY, translationY); pa.start(); } } @@ -510,7 +510,7 @@ public class StackView extends AdapterViewAnimator { new PropertyValuesHolder("YProgress", finalYProgress); PropertyValuesHolder snapBackX = new PropertyValuesHolder("XProgress", 0.0f); - PropertyAnimator pa = new PropertyAnimator(duration, animationSlider, + ObjectAnimator pa = new ObjectAnimator(duration, animationSlider, snapBackX, snapBackY); pa.setInterpolator(new LinearInterpolator()); pa.start(); @@ -529,7 +529,7 @@ public class StackView extends AdapterViewAnimator { new PropertyValuesHolder("YProgress", finalYProgress); PropertyValuesHolder snapBackX = new PropertyValuesHolder("XProgress", 0.0f); - PropertyAnimator pa = new PropertyAnimator(duration, animationSlider, + ObjectAnimator pa = new ObjectAnimator(duration, animationSlider, snapBackX, snapBackY); pa.start(); } @@ -870,7 +870,7 @@ public class StackView extends AdapterViewAnimator { private Rect invalidateRect = new Rect(); private RectF invalidateRectf = new RectF(); - // This is public so that PropertyAnimator can access it + // This is public so that ObjectAnimator can access it public void setVerticalOffset(int newVerticalOffset) { int offsetDelta = newVerticalOffset - verticalOffset; verticalOffset = newVerticalOffset; -- cgit v1.1