diff options
| -rw-r--r-- | core/java/android/animation/Animatable.java | 146 | ||||
| -rwxr-xr-x | core/java/android/animation/Animator.java | 773 | ||||
| -rw-r--r-- | core/java/android/animation/DoubleEvaluator.java | 42 | ||||
| -rw-r--r-- | core/java/android/animation/FloatEvaluator.java | 42 | ||||
| -rw-r--r-- | core/java/android/animation/IntEvaluator.java | 42 | ||||
| -rw-r--r-- | core/java/android/animation/PropertyAnimator.java | 395 | ||||
| -rw-r--r-- | core/java/android/animation/RGBEvaluator.java | 59 | ||||
| -rw-r--r-- | core/java/android/animation/Sequencer.java | 681 | ||||
| -rw-r--r-- | core/java/android/animation/TypeEvaluator.java | 44 | ||||
| -rw-r--r-- | core/java/android/animation/package.html | 5 |
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> |