summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/animation/Animatable.java146
-rwxr-xr-xcore/java/android/animation/Animator.java773
-rw-r--r--core/java/android/animation/DoubleEvaluator.java42
-rw-r--r--core/java/android/animation/FloatEvaluator.java42
-rw-r--r--core/java/android/animation/IntEvaluator.java42
-rw-r--r--core/java/android/animation/PropertyAnimator.java395
-rw-r--r--core/java/android/animation/RGBEvaluator.java59
-rw-r--r--core/java/android/animation/Sequencer.java681
-rw-r--r--core/java/android/animation/TypeEvaluator.java44
-rw-r--r--core/java/android/animation/package.html5
10 files changed, 2229 insertions, 0 deletions
diff --git a/core/java/android/animation/Animatable.java b/core/java/android/animation/Animatable.java
new file mode 100644
index 000000000000..68415f009225
--- /dev/null
+++ b/core/java/android/animation/Animatable.java
@@ -0,0 +1,146 @@
+/*
+ * 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 java.util.ArrayList;
+
+/**
+ * This is the superclass for classes which provide basic support for animations which can be
+ * started, ended, and have <code>AnimatableListeners</code> added to them.
+ */
+public abstract class Animatable {
+
+
+ /**
+ * The set of listeners to be sent events through the life of an animation.
+ */
+ ArrayList<AnimatableListener> 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()}, <code>cancel()</code> 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() {
+ }
+
+ /**
+ * 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<AnimatableListener>();
+ }
+ 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 <code>Animatable</code> object.
+ *
+ * @return ArrayList<AnimatableListener> The set of listeners.
+ */
+ public ArrayList<AnimatableListener> getListeners() {
+ return mListeners;
+ }
+
+ /**
+ * Removes all listeners from this object. This is equivalent to calling
+ * <code>getListeners()</code> followed by calling <code>clear()</code> on the
+ * returned list of listeners.
+ */
+ public void removeAllListeners() {
+ if (mListeners != null) {
+ mListeners.clear();
+ mListeners = null;
+ }
+ }
+
+ /**
+ * <p>An animation listener receives notifications from an animation.
+ * Notifications indicate animation related events, such as the end or the
+ * repetition of the animation.</p>
+ */
+ public static interface AnimatableListener {
+ /**
+ * <p>Notifies the start of the animation.</p>
+ *
+ * @param animation The started animation.
+ */
+ void onAnimationStart(Animatable animation);
+
+ /**
+ * <p>Notifies the end of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * @param animation The animation which reached its end.
+ */
+ void onAnimationEnd(Animatable animation);
+
+ /**
+ * <p>Notifies the cancellation of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * @param animation The animation which was canceled.
+ */
+ void onAnimationCancel(Animatable animation);
+
+ /**
+ * <p>Notifies the repetition of the animation.</p>
+ *
+ * @param animation The animation which was repeated.
+ */
+ void onAnimationRepeat(Animatable animation);
+ }
+}
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
new file mode 100755
index 000000000000..b6c4763dd404
--- /dev/null
+++ b/core/java/android/animation/Animator.java
@@ -0,0 +1,773 @@
+/*
+ * 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.Message;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+
+/**
+ * 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.
+ */
+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
+
+ /**
+ * Internal variables
+ */
+
+
+ // 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;
+
+ // 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<Animator> sAnimations = new ArrayList<Animator>();
+
+ // The set of animations to be started on the next animation frame
+ private static final ArrayList<Animator> sPendingAnimations = new ArrayList<Animator>();
+
+ // 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<Animator> sEndingAnims = new ArrayList<Animator>();
+ private static final ArrayList<Animator> sDelayedAnims = new ArrayList<Animator>();
+ private static final ArrayList<Animator> sReadyAnims = new ArrayList<Animator>();
+
+ //
+ // Backing variables
+ //
+
+ // How long the animation should last in ms
+ private long mDuration;
+
+ // The value that the animation should start from, set in the constructor
+ private Object mValueFrom;
+
+ // The value that the animation should animate to, set in the constructor
+ private Object mValueTo;
+
+ // 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 type evaluator used to calculate the animated values. This evaluator is determined
+ * automatically based on the type of the start/end objects passed into the constructor,
+ * but the system only knows about the primitive types int, double, and float. Any other
+ * type will need to set the evaluator to a custom evaluator for that type.
+ */
+ private TypeEvaluator mEvaluator;
+
+ /**
+ * The set of listeners to be sent events through the life of an animation.
+ */
+ private ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
+
+ /**
+ * The current value calculated by the animation. The value is calculated in animateFraction(),
+ * prior to calling the setter (if set) and sending out the onAnimationUpdate() callback
+ * to the update listeners.
+ */
+ private Object mAnimatedValue = null;
+
+ /**
+ * The type of the values, as determined by the valueFrom/valueTo properties.
+ */
+ Class mValueType;
+
+ /**
+ * Public constants
+ */
+
+ /**
+ * When the animation reaches the end and <code>repeatCount</code> 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 <code>repeatCount</code> 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;
+
+ private Animator(long duration, Object valueFrom, Object valueTo, Class valueType) {
+ mDuration = duration;
+ mValueFrom = valueFrom;
+ mValueTo= valueTo;
+ this.mValueType = valueType;
+ }
+
+ /**
+ * This function is called immediately before processing the first animation
+ * frame of an animation. If there is a nonzero <code>startDelay</code>, the
+ * function is called after that delay ends.
+ * It takes care of the final initialization steps for the
+ * animation.
+ *
+ * <p>Overrides of this method should call the superclass method to ensure
+ * that internal mechanisms for the animation are set up correctly.</p>
+ */
+ void initAnimation() {
+ if (mEvaluator == null) {
+ mEvaluator = (mValueType == int.class) ? sIntEvaluator :
+ (mValueType == double.class) ? sDoubleEvaluator : sFloatEvaluator;
+ }
+ mPlayingBackwards = false;
+ mCurrentIteration = 0;
+ }
+
+ /**
+ * A constructor that takes <code>float</code> values.
+ *
+ * @param duration The length of the animation, in milliseconds.
+ * @param valueFrom The initial value of the property when the animation begins.
+ * @param valueTo The value to which the property will animate.
+ */
+ public Animator(long duration, float valueFrom, float valueTo) {
+ this(duration, valueFrom, valueTo, float.class);
+ }
+
+ /**
+ * A constructor that takes <code>int</code> values.
+ *
+ * @param duration The length of the animation, in milliseconds.
+ * @param valueFrom The initial value of the property when the animation begins.
+ * @param valueTo The value to which the property will animate.
+ */
+ public Animator(long duration, int valueFrom, int valueTo) {
+ this(duration, valueFrom, valueTo, int.class);
+ }
+
+ /**
+ * A constructor that takes <code>double</code> values.
+ *
+ * @param duration The length of the animation, in milliseconds.
+ * @param valueFrom The initial value of the property when the animation begins.
+ * @param valueTo The value to which the property will animate.
+ */
+ public Animator(long duration, double valueFrom, double valueTo) {
+ this(duration, valueFrom, valueTo, double.class);
+ }
+
+ /**
+ * A constructor that takes <code>Object</code> values.
+ *
+ * @param duration The length of the animation, in milliseconds.
+ * @param valueFrom The initial value of the property when the animation begins.
+ * @param valueTo The value to which the property will animate.
+ */
+ public Animator(long duration, Object valueFrom, Object valueTo) {
+ this(duration, valueFrom, valueTo,
+ (valueFrom != null) ? valueFrom.getClass() : valueTo.getClass());
+ }
+
+ /**
+ * 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<Animator> pendingCopy =
+ (ArrayList<Animator>) 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.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;
+ }
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Gets the value that this animation will start from.
+ *
+ * @return Object The starting value for the animation.
+ */
+ public Object getValueFrom() {
+ return mValueFrom;
+ }
+
+ /**
+ * Sets the value that this animation will start from.
+ */
+ public void setValueFrom(Object valueFrom) {
+ mValueFrom = valueFrom;
+ }
+
+ /**
+ * Gets the value that this animation will animate to.
+ *
+ * @return Object The ending value for the animation.
+ */
+ public Object getValueTo() {
+ return mValueTo;
+ }
+
+ /**
+ * Sets the value that this animation will animate to.
+ *
+ * @return Object The ending value for the animation.
+ */
+ public void setValueTo(Object valueTo) {
+ mValueTo = valueTo;
+ }
+
+ /**
+ * 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 <code>Animator</code> for the 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 <code>Animator</code>
+ * 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 <code>Animator</code> for
+ * the property specified in the constructor.
+ */
+ public Object getAnimatedValue() {
+ return mAnimatedValue;
+ }
+
+ /**
+ * 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<AnimatorUpdateListener>();
+ }
+ 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
+ */
+ public void setInterpolator(Interpolator value) {
+ if (value != null) {
+ mInterpolator = value;
+ }
+ }
+
+ /**
+ * 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 <code>startValue</code> and <code>endValue</code> 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.
+ *
+ * @param value the evaluator to be used this animation
+ */
+ public void setEvaluator(TypeEvaluator value) {
+ if (value != null) {
+ mEvaluator = value;
+ }
+ }
+
+ public void start() {
+ 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);
+ }
+
+ public void cancel() {
+ if (mListeners != null) {
+ ArrayList<AnimatableListener> tmpListeners =
+ (ArrayList<AnimatableListener>) mListeners.clone();
+ for (AnimatableListener 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;
+ }
+
+ public void end() {
+ // Just set the ENDED flag - this causes the animation to end the next time a frame
+ // is processed.
+ mPlayingState = ENDED;
+ }
+
+ /**
+ * 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);
+ if (mListeners != null) {
+ ArrayList<AnimatableListener> tmpListeners =
+ (ArrayList<AnimatableListener>) mListeners.clone();
+ for (AnimatableListener listener : tmpListeners) {
+ listener.onAnimationEnd(this);
+ }
+ }
+ mPlayingState = STOPPED;
+ }
+
+ /**
+ * 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<AnimatableListener> tmpListeners =
+ (ArrayList<AnimatableListener>) 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 <code>startDelay</code> 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 <code>startDelay</code> and should be started.
+ * @return True if the animation's <code>startDelay</code> has been exceeded and the animation
+ * should be added to the set of active animations.
+ */
+ private boolean delayedAnimationFrame(long currentTime) {
+ 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
+ * <code>repeatCount</code> has been exceeded and the animation should be ended.
+ */
+ private boolean animationFrame(long currentTime) {
+
+ boolean done = false;
+
+ if (mPlayingState == STOPPED) {
+ mPlayingState = RUNNING;
+ mStartTime = currentTime;
+ }
+ switch (mPlayingState) {
+ case RUNNING:
+ 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;
+ 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 <code>end()</code>
+ * function is called, to set the final value on the property.
+ *
+ * <p>Overrides of this method must call the superclass to perform the calculation
+ * of the animated value.</p>
+ *
+ * @param fraction The elapsed fraction of the animation.
+ */
+ void animateValue(float fraction) {
+ fraction = mInterpolator.getInterpolation(fraction);
+ mAnimatedValue = mEvaluator.evaluate(fraction, mValueFrom, mValueTo);
+ if (mUpdateListeners != null) {
+ int numListeners = mUpdateListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ mUpdateListeners.get(i).onAnimationUpdate(this);
+ }
+ }
+ }
+
+ /**
+ * Implementors of this interface can add themselves as update listeners
+ * to an <code>Animator</code> instance to receive callbacks on every animation
+ * frame, after the current frame's values have been calculated for that
+ * <code>Animator</code>.
+ */
+ public static interface AnimatorUpdateListener {
+ /**
+ * <p>Notifies the occurrence of another frame of the animation.</p>
+ *
+ * @param animation The animation which was repeated.
+ */
+ void onAnimationUpdate(Animator animation);
+
+ }
+} \ No newline at end of file
diff --git a/core/java/android/animation/DoubleEvaluator.java b/core/java/android/animation/DoubleEvaluator.java
new file mode 100644
index 000000000000..86e3f225e64c
--- /dev/null
+++ b/core/java/android/animation/DoubleEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * 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 evaluator can be used to perform type interpolation between <code>double</code> values.
+ */
+public class DoubleEvaluator implements TypeEvaluator {
+ /**
+ * This function returns the result of linearly interpolating the start and end values, with
+ * <code>fraction</code> representing the proportion between the start and end values. The
+ * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+ * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+ * and <code>t</code> is <code>fraction</code>.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value; should be of type <code>double</code> or
+ * <code>Double</code>
+ * @param endValue The end value; should be of type <code>double</code> or
+ * <code>Double</code>
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ public Object evaluate(float fraction, Object startValue, Object endValue) {
+ double startDouble = (Double) startValue;
+ return startDouble + fraction * ((Double) endValue - startDouble);
+ }
+} \ No newline at end of file
diff --git a/core/java/android/animation/FloatEvaluator.java b/core/java/android/animation/FloatEvaluator.java
new file mode 100644
index 000000000000..29a6f71c2246
--- /dev/null
+++ b/core/java/android/animation/FloatEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * 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 evaluator can be used to perform type interpolation between <code>float</code> values.
+ */
+public class FloatEvaluator implements TypeEvaluator {
+
+ /**
+ * This function returns the result of linearly interpolating the start and end values, with
+ * <code>fraction</code> representing the proportion between the start and end values. The
+ * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+ * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+ * and <code>t</code> is <code>fraction</code>.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value; should be of type <code>float</code> or
+ * <code>Float</code>
+ * @param endValue The end value; should be of type <code>float</code> or <code>Float</code>
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ public Object evaluate(float fraction, Object startValue, Object endValue) {
+ float startFloat = (Float) startValue;
+ return startFloat + fraction * ((Float) endValue - startFloat);
+ }
+} \ No newline at end of file
diff --git a/core/java/android/animation/IntEvaluator.java b/core/java/android/animation/IntEvaluator.java
new file mode 100644
index 000000000000..7a2911a5a2b3
--- /dev/null
+++ b/core/java/android/animation/IntEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * 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 evaluator can be used to perform type interpolation between <code>int</code> values.
+ */
+public class IntEvaluator implements TypeEvaluator {
+
+ /**
+ * This function returns the result of linearly interpolating the start and end values, with
+ * <code>fraction</code> representing the proportion between the start and end values. The
+ * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+ * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+ * and <code>t</code> is <code>fraction</code>.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value; should be of type <code>int</code> or
+ * <code>Integer</code>
+ * @param endValue The end value; should be of type <code>int</code> or <code>Integer</code>
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ public Object evaluate(float fraction, Object startValue, Object endValue) {
+ int startInt = (Integer) startValue;
+ return (int) (startInt + fraction * ((Integer) endValue - startInt));
+ }
+} \ No newline at end of file
diff --git a/core/java/android/animation/PropertyAnimator.java b/core/java/android/animation/PropertyAnimator.java
new file mode 100644
index 000000000000..99799f0b747a
--- /dev/null
+++ b/core/java/android/animation/PropertyAnimator.java
@@ -0,0 +1,395 @@
+/*
+ * 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.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * 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;
+
+ private Method mGetter = null;
+
+ // The property setter that is assigned internally, based on the propertyName passed into
+ // the constructor
+ private Method mSetter;
+
+ // These maps hold all property entries for a particular class. This map
+ // is used to speed up property/setter/getter lookups for a given class/property
+ // combination. No need to use reflection on the combination more than once.
+ private static final HashMap<Object, HashMap<String, Method>> sSetterPropertyMap =
+ new HashMap<Object, HashMap<String, Method>>();
+ private static final HashMap<Object, HashMap<String, Method>> sGetterPropertyMap =
+ new HashMap<Object, HashMap<String, Method>>();
+
+ // This lock is used to ensure that only one thread is accessing the property maps
+ // at a time.
+ private ReentrantReadWriteLock propertyMapLock = new ReentrantReadWriteLock();
+
+
+ /**
+ * Sets the name of the property that will be animated. This name is used to derive
+ * a setter function that will be called to set animated values.
+ * For example, a property name of <code>foo</code> will result
+ * in a call to the function <code>setFoo()</code> on the target object. If either
+ * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+ * also be derived and called.
+ *
+ * <p>Note that the setter function derived from this property name
+ * must take the same parameter type as the
+ * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
+ * the setter function will fail.</p>
+ *
+ * @param propertyName The name of the property being animated.
+ */
+ public void setPropertyName(String propertyName) {
+ 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 <code>foo</code> will result
+ * in a call to the function <code>setFoo()</code> on the target object. If either
+ * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+ * also be derived and called.
+ */
+ public String getPropertyName() {
+ return mPropertyName;
+ }
+
+ /**
+ * Sets the <code>Method</code> that is called with the animated values calculated
+ * during the animation. Setting the setter method is an alternative to supplying a
+ * {@link #setPropertyName(String) propertyName} from which the method is derived. This
+ * approach is more direct, and is especially useful when a function must be called that does
+ * not correspond to the convention of <code>setName()</code>. For example, if a function
+ * called <code>offset()</code> is to be called with the animated values, there is no way
+ * to tell <code>PropertyAnimator</code> how to call that function simply through a property
+ * name, so a setter method should be supplied instead.
+ *
+ * <p>Note that the setter function must take the same parameter type as the
+ * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
+ * the setter function will fail.</p>
+ *
+ * @param setter The setter method that should be called with the animated values.
+ */
+ public void setSetter(Method setter) {
+ mSetter = setter;
+ }
+
+ /**
+ * Gets the <code>Method</code> that is called with the animated values calculated
+ * during the animation.
+ */
+ public Method getSetter() {
+ return mSetter;
+ }
+
+ /**
+ * Sets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
+ * <code>valueTo</code> properties. Setting the getter method is an alternative to supplying a
+ * {@link #setPropertyName(String) propertyName} from which the method is derived. This
+ * approach is more direct, and is especially useful when a function must be called that does
+ * not correspond to the convention of <code>setName()</code>. For example, if a function
+ * called <code>offset()</code> is to be called to get an initial value, there is no way
+ * to tell <code>PropertyAnimator</code> how to call that function simply through a property
+ * name, so a getter method should be supplied instead.
+ *
+ * <p>Note that the getter method is only called whether supplied here or derived
+ * from the property name, if one of <code>valueFrom</code> or <code>valueTo</code> are
+ * null. If both of those values are non-null, then there is no need to get one of the
+ * values and the getter is not called.
+ *
+ * <p>Note that the getter function must return the same parameter type as the
+ * <code>valueFrom</code> and <code>valueTo</code> properties (whichever of them are
+ * non-null), otherwise the call to the getter function will fail.</p>
+ *
+ * @param getter The getter method that should be called to get initial animation values.
+ */
+ public void setGetter(Method getter) {
+ mGetter = getter;
+ }
+
+ /**
+ * Gets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
+ * <code>valueTo</code> properties.
+ */
+ public Method getGetter() {
+ return mGetter;
+ }
+
+ /**
+ * 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) {
+ // 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[] = new Class[1];
+ args[0] = mValueType;
+ try {
+ returnVal = mTarget.getClass().getMethod(setterName, args);
+ } catch (NoSuchMethodException e) {
+ Log.e("PropertyAnimator",
+ "Couldn't find setter for property " + mPropertyName + ": " + e);
+ }
+ return returnVal;
+ }
+
+ /**
+ * A constructor that takes <code>float</code> values. When this constructor
+ * is called, the system expects to find a setter for <code>propertyName</code> on
+ * the target object that takes a <code>float</code> value.
+ *
+ * @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 function on it called <code>setName()</code>, where <code>name</code> is
+ * the name of the property passed in as the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property on the <code>target</code> object
+ * that will be animated. Given this name, the constructor will search for a
+ * setter on the target object with the name <code>setPropertyName</code>. For example,
+ * if the constructor is called with <code>propertyName = "foo"</code>, then the
+ * target object should have a setter function with the name <code>setFoo()</code>.
+ * @param valueFrom The initial value of the property when the animation begins.
+ * @param valueTo The value to which the property will animate.
+ */
+ public PropertyAnimator(int duration, Object target, String propertyName,
+ float valueFrom, float valueTo) {
+ super(duration, valueFrom, valueTo);
+ mTarget = target;
+ mPropertyName = propertyName;
+ }
+
+ /**
+ * A constructor that takes <code>int</code> values. When this constructor
+ * is called, the system expects to find a setter for <code>propertyName</code> on
+ * the target object that takes a <code>int</code> value.
+ *
+ * @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 function on it called <code>setName()</code>, where <code>name</code> is
+ * the name of the property passed in as the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property on the <code>target</code> object
+ * that will be animated. Given this name, the constructor will search for a
+ * setter on the target object with the name <code>setPropertyName</code>. For example,
+ * if the constructor is called with <code>propertyName = "foo"</code>, then the
+ * target object should have a setter function with the name <code>setFoo()</code>.
+ * @param valueFrom The initial value of the property when the animation begins.
+ * @param valueTo The value to which the property will animate.
+ */
+ public PropertyAnimator(int duration, Object target, String propertyName,
+ int valueFrom, int valueTo) {
+ super(duration, valueFrom, valueTo);
+ mTarget = target;
+ mPropertyName = propertyName;
+ }
+
+ /**
+ * A constructor that takes <code>double</code> values. When this constructor
+ * is called, the system expects to find a setter for <code>propertyName</code> on
+ * the target object that takes a <code>double</code> value.
+ *
+ * @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 function on it called <code>setName()</code>, where <code>name</code> is
+ * the name of the property passed in as the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property on the <code>target</code> object
+ * that will be animated. Given this name, the constructor will search for a
+ * setter on the target object with the name <code>setPropertyName</code>. For example,
+ * if the constructor is called with <code>propertyName = "foo"</code>, then the
+ * target object should have a setter function with the name <code>setFoo()</code>.
+ * @param valueFrom The initial value of the property when the animation begins.
+ * @param valueTo The value to which the property will animate.
+ */
+ public PropertyAnimator(int duration, Object target, String propertyName,
+ double valueFrom, double valueTo) {
+ super(duration, valueFrom, valueTo);
+ mTarget = target;
+ mPropertyName = propertyName;
+ }
+
+ /**
+ * A constructor that takes <code>Object</code> values. When this constructor
+ * is called, the system expects to find a setter for <code>propertyName</code> on
+ * the target object that takes a value of the same type as the <code>Object</code>s.
+ *
+ * @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 function on it called <code>setName()</code>, where <code>name</code> is
+ * the name of the property passed in as the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property on the <code>target</code> object
+ * that will be animated. Given this name, the constructor will search for a
+ * setter on the target object with the name <code>setPropertyName</code>. For example,
+ * if the constructor is called with <code>propertyName = "foo"</code>, then the
+ * target object should have a setter function with the name <code>setFoo()</code>.
+ * @param valueFrom The initial value of the property when the animation begins.
+ * @param valueTo The value to which the property will animate.
+ */
+ public PropertyAnimator(int duration, Object target, String propertyName,
+ Object valueFrom, Object valueTo) {
+ super(duration, valueFrom, valueTo);
+ mTarget = target;
+ mPropertyName = propertyName;
+ }
+
+ /**
+ * This function is called immediately before processing the first animation
+ * frame of an animation. If there is a nonzero <code>startDelay</code>, 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.
+ *
+ * <p>Overriders of this method should call the superclass method to cause
+ * internal mechanisms to be set up correctly.</p>
+ */
+ @Override
+ void initAnimation() {
+ super.initAnimation();
+ if (mSetter == null) {
+ try {
+ // Have to lock property map prior to reading it, to guard against
+ // another thread putting something in there after we've checked it
+ // but before we've added an entry to it
+ propertyMapLock.writeLock().lock();
+ HashMap<String, Method> propertyMap = sSetterPropertyMap.get(mTarget);
+ if (propertyMap != null) {
+ mSetter = propertyMap.get(mPropertyName);
+ if (mSetter != null) {
+ return;
+ }
+ }
+ mSetter = getPropertyFunction("set");
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Method>();
+ sSetterPropertyMap.put(mTarget, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mSetter);
+ } finally {
+ propertyMapLock.writeLock().unlock();
+ }
+ }
+ if (getValueFrom() == null || getValueTo() == null) {
+ // Need to set up the getter if not set by the user, then call it
+ // to get the initial values
+ if (mGetter == null) {
+ try {
+ propertyMapLock.writeLock().lock();
+ HashMap<String, Method> propertyMap = sGetterPropertyMap.get(mTarget);
+ if (propertyMap != null) {
+ mGetter = propertyMap.get(mPropertyName);
+ if (mGetter != null) {
+ return;
+ }
+ }
+ mGetter = getPropertyFunction("get");
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Method>();
+ sGetterPropertyMap.put(mTarget, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mGetter);
+ } finally {
+ propertyMapLock.writeLock().unlock();
+ }
+ }
+ try {
+ if (getValueFrom() == null) {
+ setValueFrom(mGetter.invoke(mTarget));
+ }
+ if (getValueTo() == null) {
+ setValueTo(mGetter.invoke(mTarget));
+ }
+ } catch (IllegalArgumentException e) {
+ Log.e("PropertyAnimator", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyAnimator", e.toString());
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyAnimator", e.toString());
+ }
+ }
+ }
+
+
+ /**
+ * The target object whose property will be animated by this animation
+ *
+ * @return The object being animated
+ */
+ public Object getTarget() {
+ return 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 <code>end()</code>
+ * function is called, to set the final value on the property.
+ *
+ * <p>Overrides of this method must call the superclass to perform the calculation
+ * of the animated value.</p>
+ *
+ * @param fraction The elapsed fraction of the animation.
+ */
+ @Override
+ void animateValue(float fraction) {
+ super.animateValue(fraction);
+ if (mSetter != null) {
+ try {
+ mSetter.invoke(mTarget, getAnimatedValue());
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyAnimator", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyAnimator", e.toString());
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Animator: target: " + this.mTarget + "\n" +
+ " property: " + mPropertyName + "\n" +
+ " from: " + getValueFrom() + "\n" +
+ " to: " + getValueTo();
+ }
+}
diff --git a/core/java/android/animation/RGBEvaluator.java b/core/java/android/animation/RGBEvaluator.java
new file mode 100644
index 000000000000..bae0af0c8b36
--- /dev/null
+++ b/core/java/android/animation/RGBEvaluator.java
@@ -0,0 +1,59 @@
+/*
+ * 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 evaluator can be used to perform type interpolation between integer
+ * values that represent ARGB colors.
+ */
+public class RGBEvaluator implements TypeEvaluator {
+
+ /**
+ * This function returns the calculated in-between value for a color
+ * given integers that represent the start and end values in the four
+ * bytes of the 32-bit int. Each channel is separately linearly interpolated
+ * and the resulting calculated values are recombined into the return value.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue A 32-bit int value representing colors in the
+ * separate bytes of the parameter
+ * @param endValue A 32-bit int value representing colors in the
+ * separate bytes of the parameter
+ * @return A value that is calculated to be the linearly interpolated
+ * result, derived by separating the start and end values into separate
+ * color channels and interpolating each one separately, recombining the
+ * resulting values in the same way.
+ */
+ public Object evaluate(float fraction, Object startValue, Object endValue) {
+ int startInt = (Integer) startValue;
+ int startA = (startInt >> 24);
+ int startR = (startInt >> 16) & 0xff;
+ int startG = (startInt >> 8) & 0xff;
+ int startB = startInt & 0xff;
+
+ int endInt = (Integer) endValue;
+ int endA = (endInt >> 24);
+ int endR = (endInt >> 16) & 0xff;
+ int endG = (endInt >> 8) & 0xff;
+ int endB = endInt & 0xff;
+
+ return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
+ (int)((startR + (int)(fraction * (endR - startR))) << 16) |
+ (int)((startG + (int)(fraction * (endG - startG))) << 8) |
+ (int)((startB + (int)(fraction * (endB - startB))));
+ }
+} \ No newline at end of file
diff --git a/core/java/android/animation/Sequencer.java b/core/java/android/animation/Sequencer.java
new file mode 100644
index 000000000000..00ab6a3342da
--- /dev/null
+++ b/core/java/android/animation/Sequencer.java
@@ -0,0 +1,681 @@
+/*
+ * 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 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.
+ *
+ * <p>There are two different approaches to adding animations to a <code>Sequencer</code>:
+ * 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.</p>
+ *
+ * <p>It is possible to set up a <code>Sequencer</code> 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 {
+
+ /**
+ * Tracks aniamtions currently being played, so that we know what to
+ * cancel or end when cancel() or end() is called on this Sequencer
+ */
+ private final ArrayList<Animatable> mPlayingSet = new ArrayList<Animatable>();
+
+ /**
+ * 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 final HashMap<Animatable, Node> mNodeMap = new HashMap<Animatable, Node>();
+
+ /**
+ * 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 final ArrayList<Node> mNodes = new ArrayList<Node>();
+
+ /**
+ * 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 final ArrayList<Node> mSortedNodes = new ArrayList<Node>();
+
+ /**
+ * The set of listeners to be sent events through the life of an animation.
+ */
+ private ArrayList<AnimatableListener> mListeners = null;
+
+ /**
+ * 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;
+
+ /**
+ * 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]);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method creates a <code>Builder</code> object, which is used to
+ * set up playing constraints. This initial <code>play()</code> method
+ * tells the <code>Builder</code> the animation that is the dependency for
+ * the succeeding commands to the <code>Builder</code>. For example,
+ * calling <code>play(a1).with(a2)</code> sets up the Sequence to play
+ * <code>a1</code> and <code>a2</code> at the same time,
+ * <code>play(a1).before(a2)</code> sets up the Sequence to play
+ * <code>a1</code> first, followed by <code>a2</code>, and
+ * <code>play(a1).after(a2)</code> sets up the Sequence to play
+ * <code>a2</code> first, followed by <code>a1</code>.
+ *
+ * <p>Note that <code>play()</code> is the only way to tell the
+ * <code>Builder</code> the animation upon which the dependency is created,
+ * so successive calls to the various functions in <code>Builder</code>
+ * will all refer to the initial parameter supplied in <code>play()</code>
+ * as the dependency of the other animations. For example, calling
+ * <code>play(a1).before(a2).before(a3)</code> will play both <code>a2</code>
+ * and <code>a3</code> when a1 ends; it does not set up a dependency between
+ * <code>a2</code> and <code>a3</code>.</p>
+ *
+ * @param anim The animation that is the dependency used in later calls to the
+ * methods in the returned <code>Builder</code> object. A null parameter will result
+ * in a null <code>Builder</code> return value.
+ * @return Builder The object that constructs the sequence based on the dependencies
+ * outlined in the calls to <code>play</code> and the other methods in the
+ * <code>Builder</code object.
+ */
+ public Builder play(Animatable anim) {
+ if (anim != null) {
+ mNeedsSort = true;
+ return new Builder(anim);
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Note that canceling a <code>Sequencer</code> also cancels all of the animations that it is
+ * responsible for.</p>
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public void cancel() {
+ if (mListeners != null) {
+ ArrayList<AnimatableListener> tmpListeners =
+ (ArrayList<AnimatableListener>) mListeners.clone();
+ for (AnimatableListener listener : tmpListeners) {
+ listener.onAnimationCancel(this);
+ }
+ }
+ if (mPlayingSet.size() > 0) {
+ for (Animatable item : mPlayingSet) {
+ item.cancel();
+ }
+ mPlayingSet.clear();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Note that ending a <code>Sequencer</code> also ends all of the animations that it is
+ * responsible for.</p>
+ */
+ @Override
+ public void end() {
+ if (mPlayingSet.size() > 0) {
+ for (Animatable item : mPlayingSet) {
+ item.end();
+ }
+ mPlayingSet.clear();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Starting this <code>Sequencer</code> 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() {
+ // 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.
+ ArrayList<Node> nodesToStart = new ArrayList<Node>();
+ for (Node node : mSortedNodes) {
+ if (mSequenceListener == null) {
+ mSequenceListener = new SequencerAnimatableListener(this);
+ }
+ node.animation.addListener(mSequenceListener);
+ if (node.dependencies == null || node.dependencies.size() == 0) {
+ nodesToStart.add(node);
+ } else {
+ for (Dependency dependency : node.dependencies) {
+ dependency.node.animation.addListener(
+ new DependencyListener(node, dependency.rule));
+ }
+ node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone();
+ }
+ }
+ // Now that all dependencies are set up, start the animations that should be started.
+ for (Node node : nodesToStart) {
+ node.animation.start();
+ mPlayingSet.add(node.animation);
+ }
+ if (mListeners != null) {
+ ArrayList<AnimatableListener> tmpListeners =
+ (ArrayList<AnimatableListener>) mListeners.clone();
+ for (AnimatableListener listener : tmpListeners) {
+ listener.onAnimationStart(this);
+ }
+ }
+ }
+
+ /**
+ * 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 {
+
+ // 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(Node node, int rule) {
+ this.mNode = node;
+ this.mRule = rule;
+ }
+
+ /**
+ * If an animation that is being listened for is canceled, then this removes
+ * the listener on that animation, to avoid triggering further animations down
+ * the line when the animation ends.
+ */
+ public void onAnimationCancel(Animatable animation) {
+ Dependency dependencyToRemove = null;
+ for (Dependency dependency : mNode.tmpDependencies) {
+ if (dependency.node.animation == animation) {
+ // animation canceled - remove the dependency and listener
+ dependencyToRemove = dependency;
+ animation.removeListener(this);
+ break;
+ }
+ }
+ mNode.tmpDependencies.remove(dependencyToRemove);
+ }
+
+ /**
+ * 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) {
+ 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();
+ }
+ }
+
+ }
+
+ 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);
+ if (mPlayingSet.size() == 0) {
+ // If this was the last child animation to end, then notify listeners that this
+ // sequence ended
+ if (mListeners != null) {
+ ArrayList<AnimatableListener> tmpListeners =
+ (ArrayList<AnimatableListener>) 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<Node> roots = new ArrayList<Node>();
+ for (Node node : mNodes) {
+ if (node.dependencies == null || node.dependencies.size() == 0) {
+ roots.add(node);
+ }
+ }
+ ArrayList<Node> tmpRoots = new ArrayList<Node>();
+ 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.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<Node>();
+ }
+ if (!node.nodeDependencies.contains(dependency.node)) {
+ node.nodeDependencies.add(dependency.node);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 {
+ 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<Dependency> 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<Dependency> 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<Node> 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<Node> nodeDependents = null;
+
+ /**
+ * 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<Dependency>();
+ nodeDependencies = new ArrayList<Node>();
+ }
+ dependencies.add(dependency);
+ if (!nodeDependencies.contains(dependency.node)) {
+ nodeDependencies.add(dependency.node);
+ }
+ Node dependencyNode = dependency.node;
+ if (dependencyNode.nodeDependents == null) {
+ dependencyNode.nodeDependents = new ArrayList<Node>();
+ }
+ dependencyNode.nodeDependents.add(this);
+ }
+ }
+
+ /**
+ * The <code>Builder</code> object is a utility class to facilitate adding animations to a
+ * <code>Sequencer</code> along with the relationships between the various animations. The
+ * intention of the <code>Builder</code> methods, along with the {@link
+ * Sequencer#play(Animatable) play()} method of <code>Sequencer</code> 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.
+ * <p/>
+ * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed
+ * internally via a call to {@link Sequencer#play(Animatable)}.</p>
+ * <p/>
+ * <p>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:</p>
+ * <pre>
+ * Sequencer s = new Sequencer();
+ * s.play(anim1).with(anim2);
+ * s.play(anim2).before(anim3);
+ * s.play(anim4).after(anim3);
+ * </pre>
+ * <p/>
+ * <p>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.</p>
+ * <p/>
+ * <p>It is possible to make several calls into the same <code>Builder</code> 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 <code>Builder</code> object. For example, the following code starts both anim2
+ * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and
+ * anim3:
+ * <pre>
+ * Sequencer s = new Sequencer();
+ * s.play(anim1).before(anim2).before(anim3);
+ * </pre>
+ * If the desired result is to play anim1 then anim2 then anim3, this code expresses the
+ * relationship correctly:</p>
+ * <pre>
+ * Sequencer s = new Sequencer();
+ * s.play(anim1).before(anim2);
+ * s.play(anim2).before(anim3);
+ * </pre>
+ * <p/>
+ * <p>Note that it is possible to express relationships that cannot be resolved and will not
+ * result in sensible results. For example, <code>play(anim1).after(anim1)</code> 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.</p>
+ */
+ 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 <code>Builder</code> 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 <code>Builder</code> 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 <code>Builder</code> 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 <code>Builder</code> 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
+ Animator anim = new Animator(delay, 0, 1);
+ Node node = new Node(anim);
+ mNodes.add(node);
+ Dependency dependency = new Dependency(node, Dependency.AFTER);
+ mCurrentNode.addDependency(dependency);
+ }
+
+ }
+
+}
diff --git a/core/java/android/animation/TypeEvaluator.java b/core/java/android/animation/TypeEvaluator.java
new file mode 100644
index 000000000000..6150e00da89d
--- /dev/null
+++ b/core/java/android/animation/TypeEvaluator.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+/**
+ * Interface for use with the {@link Animator#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)
+ */
+public interface TypeEvaluator {
+
+ /**
+ * This function returns the result of linearly interpolating the start and end values, with
+ * <code>fraction</code> representing the proportion between the start and end values. The
+ * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+ * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+ * and <code>t</code> is <code>fraction</code>.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value.
+ * @param endValue The end value.
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ public Object evaluate(float fraction, Object startValue, Object endValue);
+
+} \ No newline at end of file
diff --git a/core/java/android/animation/package.html b/core/java/android/animation/package.html
new file mode 100644
index 000000000000..1c9bf9dad835
--- /dev/null
+++ b/core/java/android/animation/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>