// Copyright 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.compositor.layouts; import android.os.SystemClock; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; /** * A class to handle simple animation sets. This can animate any object passed in by overriding * the ChromeAnimation.Animation object. * * @param <T> The type of Object being animated by this ChromeAnimation set. */ @SuppressWarnings("unchecked") public class ChromeAnimation<T> { /** * The amount we jump into the animation for the first frame. We do this as we assume * the object be animated is already resting in the initial value specified. To avoid wasting * a frame of animation on something that will look exactly the same, we jump into the * animation by this frame offset (calculated by the desired time required to draw one frame * at 60 FPS). */ private static final int FIRST_FRAME_OFFSET_MS = 1000 / 60; /** * Can be used to slow down created animations for debugging purposes. */ private static final int ANIMATION_MULTIPLIER = 1; private final AtomicBoolean mFinishCalled = new AtomicBoolean(); private final ArrayList<Animation<T>> mAnimations = new ArrayList<Animation<T>>(); private long mCurrentTime; // Keep a reference to one of each standard interpolator to avoid allocations. private static AccelerateInterpolator sAccelerateInterpolator; private static LinearInterpolator sLinearInterpolator; private static DecelerateInterpolator sDecelerateInterpolator; private static final Object sLock = new Object(); /** * @return The default acceleration interpolator. No allocation. */ public static AccelerateInterpolator getAccelerateInterpolator() { synchronized (sLock) { if (sAccelerateInterpolator == null) { sAccelerateInterpolator = new AccelerateInterpolator(); } } return sAccelerateInterpolator; } /** * @return The default linear interpolator. No allocation. */ public static LinearInterpolator getLinearInterpolator() { synchronized (sLock) { if (sLinearInterpolator == null) { sLinearInterpolator = new LinearInterpolator(); } } return sLinearInterpolator; } /** * @return The default deceleration interpolator. No allocation. */ public static DecelerateInterpolator getDecelerateInterpolator() { synchronized (sLock) { if (sDecelerateInterpolator == null) { sDecelerateInterpolator = new DecelerateInterpolator(); } } return sDecelerateInterpolator; } /** * Adds a ChromeAnimation.Animation instance to this ChromeAnimation set. This Animation will * be managed by this ChromeAnimation from now on. * * @param a The ChromeAnimation.Animation object to be controlled and updated by this * ChromeAnimation. */ public void add(ChromeAnimation.Animation<T> a) { mAnimations.add(a); } /** * Starts all of the Animation instances in this ChromeAnimation. This sets up the appropriate * state so that calls to update() can properly track how much time has passed and set the * initial values for each Animation. */ public void start() { mFinishCalled.set(false); mCurrentTime = 0; for (int i = 0; i < mAnimations.size(); ++i) { Animation<T> a = mAnimations.get(i); a.start(); } } /** * Aborts all animations of a specific type. This does not call finish on the animation. * So the animation will not reach its final value. * @param object object to find animations to be aborted. If null, matches all the animations. * @param property property to find animations to be aborted. */ public <V extends Enum<?>> void cancel(T object, V property) { for (int i = mAnimations.size() - 1; i >= 0; i--) { Animation<T> animation = mAnimations.get(i); if ((object == null || animation.getAnimatedObject() == object) && animation.checkProperty(property)) { mAnimations.remove(i); } } } /** * Forces each Animation to finish itself, setting the properties to the final value of the * Animation. */ public void updateAndFinish() { for (int i = 0; i < mAnimations.size(); ++i) { mAnimations.get(i).updateAndFinish(); } finishInternal(); } /** * Updates each Animation based on how much time has passed. Each animation gets passed the * delta since the last call to update() and can appropriately interpolate their values. The * time reference is implicitly the actual uptime of the app. * * @return Whether or not this ChromeAnimation is finished animating. */ public boolean update() { return update(SystemClock.uptimeMillis()); } /** * Updates each Animation based on how much time has passed. Each animation gets passed the * delta since the last call to update() and can appropriately interpolate their values. * * @param time The current time of the app in ms. * @return Whether or not this ChromeAnimation is finished animating. */ public boolean update(long time) { if (mFinishCalled.get()) { return true; } if (mCurrentTime == 0) mCurrentTime = time - FIRST_FRAME_OFFSET_MS; long dtMs = time - mCurrentTime; mCurrentTime += dtMs; boolean finished = true; for (int i = 0; i < mAnimations.size(); ++i) { mAnimations.get(i).update(dtMs); finished &= mAnimations.get(i).finished(); } if (finished) { updateAndFinish(); } return false; } /** * @return Whether or not this ChromeAnimation is finished animating. */ public boolean finished() { if (mFinishCalled.get()) { return true; } for (int i = 0; i < mAnimations.size(); ++i) { if (!mAnimations.get(i).finished()) { return false; } } return true; } private void finishInternal() { if (mFinishCalled.get()) return; finish(); mFinishCalled.set(true); } /** * Callback to handle any necessary cleanups upon finishing the animation. * * <p> * Called as part of {@link #update()} if the end of the animation is reached or * {@link #updateAndFinish()}. */ protected void finish() { } /** * A particular animation instance, meant to be managed by a ChromeAnimation object. * * @param <T> The type of Object being animated by this Animation instance. This object should * be accessed inside setProperty() where it can be manipulated by the new value. */ public abstract static class Animation<T> { protected T mAnimatedObject; private float mStart; private float mEnd; private long mCurrentTime; private long mDuration; private long mStartDelay; private boolean mDelayStartValue; private boolean mHasFinished; private Interpolator mInterpolator = getDecelerateInterpolator(); /** * Creates a new Animation object with a custom Interpolator. * * @param t The object to be Animated. * @param start The starting value of the animation. * @param end The ending value of the animation. * @param duration The duration of the animation. This does not include the startTime. * The duration must be strictly positive. * @param startTime The time at which this animation should start. * @param interpolator The Interpolator instance to use for animating the property from * start to finish. */ public Animation(T t, float start, float end, long duration, long startTime, Interpolator interpolator) { this(t, start, end, duration, startTime); mInterpolator = interpolator; } /** * Creates a new Animation object. * * @param t The object to be Animated. * @param start The starting value of the animation. * @param end The ending value of the animation. * @param duration The duration of the animation. This does not include the startTime. * The duration must be strictly positive. * @param startTime The time at which this animation should start. */ public Animation(T t, float start, float end, long duration, long startTime) { assert duration > 0; mAnimatedObject = t; mStart = start; mEnd = end; mDuration = duration * ANIMATION_MULTIPLIER; mStartDelay = startTime * ANIMATION_MULTIPLIER; mCurrentTime = 0; } /** * Returns the object being animated. */ protected T getAnimatedObject() { return mAnimatedObject; } /** * @param delayStartValue Whether to delay setting the animation's initial value until * after the specified start delay (Default = false). */ public void setStartValueAfterStartDelay(boolean delayStartValue) { mDelayStartValue = delayStartValue; } /** * Sets the internal timer to be at the end of the animation and calls setProperty with the * final value. */ public void updateAndFinish() { mCurrentTime = mDuration + mStartDelay; setProperty(mEnd); } /** * Updates the internal timer based on the time delta passed in. The proper property value * is interpolated and passed to setProperty. * * @param dtMs The amount of time, in milliseconds, that this animation should be * progressed. */ public void update(long dtMs) { mCurrentTime += dtMs; // Bound our time here so that our scale never goes above 1.0. mCurrentTime = Math.min(mCurrentTime, mDuration + mStartDelay); if (mDelayStartValue && mCurrentTime < mStartDelay) { return; } // Figure out the relative fraction of time we need to animate. long relativeTime = Math.max(0, Math.min(mCurrentTime - mStartDelay, mDuration)); setProperty(mStart + (mEnd - mStart) * mInterpolator.getInterpolation((float) relativeTime / (float) mDuration)); } /** * Starts the animation and calls setProperty() with the initial value. */ public void start() { mHasFinished = false; mCurrentTime = 0; update(0); } /** * @return Whether or not this current animation is finished. */ public boolean finished() { if (!mHasFinished && mCurrentTime >= mDuration + mStartDelay) { mHasFinished = true; onPropertyAnimationFinished(); } return mHasFinished; } /** * Checks if the given property is being animated. */ public <V extends Enum<?>> boolean checkProperty(V prop) { return true; } /** * The abstract method that gets called with the new interpolated value based on the * current time. This gives inheriting classes the chance to set a property on the * animating object. * * @param p The current animated value based on the current time and the Interpolator. */ public abstract void setProperty(float p); /** * The abstract method that gets called when the property animation finished. */ public abstract void onPropertyAnimationFinished(); } /** * Provides a interface for updating animatible properties. * * @param <T> The {@link Enum} of animatable properties. */ public static interface Animatable<T extends Enum<?>> { /** * Updates an animatable property. * * @param prop The property to update * @param val The new value */ public void setProperty(T prop, float val); /** * Notifies that the animation for a certain property has finished. * * @param prop The property that has finished animating. */ public void onPropertyAnimationFinished(T prop); } /** * An animation that can be applied to {@link ChromeAnimation.Animatable} objects. * * @param <V> The type of {@link ChromeAnimation.Animatable} object to animate. */ public static class AnimatableAnimation<V extends Enum<?>> extends Animation<Animatable<V>> { private final V mProperty; /** * @param animatable The object being animated * @param property The property being animated * @param start The starting value of the animation * @param end The ending value of the animation * @param duration The duration of the animation. This does not include the startTime. * @param startTime The time at which this animation should start. * @param interpolator The interpolator to use for the animation */ public AnimatableAnimation(Animatable<V> animatable, V property, float start, float end, long duration, long startTime, Interpolator interpolator) { super(animatable, start, end, duration, startTime, interpolator); mProperty = property; } @Override public void setProperty(float p) { mAnimatedObject.setProperty(mProperty, p); } @Override public void onPropertyAnimationFinished() { mAnimatedObject.onPropertyAnimationFinished(mProperty); } /** * Helper method to add an {@link ChromeAnimation.AnimatableAnimation} * to a {@link ChromeAnimation} * * @param <T> The Enum type of the Property being used * @param set The set to add the animation to * @param object The object being animated * @param prop The property being animated * @param start The starting value of the animation * @param end The ending value of the animation * @param duration The duration of the animation in ms * @param startTime The start time in ms */ public static <T extends Enum<?>> void addAnimation(ChromeAnimation<Animatable<?>> set, Animatable<T> object, T prop, float start, float end, long duration, long startTime) { addAnimation(set, object, prop, start, end, duration, startTime, false); } /** * Helper method to add an {@link ChromeAnimation.AnimatableAnimation} * to a {@link ChromeAnimation} * * @param <T> The Enum type of the Property being used * @param set The set to add the animation to * @param object The object being animated * @param prop The property being animated * @param start The starting value of the animation * @param end The ending value of the animation * @param duration The duration of the animation in ms * @param startTime The start time in ms * @param setStartValueAfterStartDelay See * {@link ChromeAnimation.Animation#setStartValueAfterStartDelay(boolean)} */ public static <T extends Enum<?>> void addAnimation(ChromeAnimation<Animatable<?>> set, Animatable<T> object, T prop, float start, float end, long duration, long startTime, boolean setStartValueAfterStartDelay) { addAnimation(set, object, prop, start, end, duration, startTime, setStartValueAfterStartDelay, getDecelerateInterpolator()); } /** * Helper method to add an {@link ChromeAnimation.AnimatableAnimation} * to a {@link ChromeAnimation} * * @param <T> The Enum type of the Property being used * @param set The set to add the animation to * @param object The object being animated * @param prop The property being animated * @param start The starting value of the animation * @param end The ending value of the animation * @param duration The duration of the animation in ms * @param startTime The start time in ms * @param setStartValueAfterStartDelay See * {@link ChromeAnimation.Animation#setStartValueAfterStartDelay(boolean)} * @param interpolator The interpolator to use for the animation */ public static <T extends Enum<?>> void addAnimation(ChromeAnimation<Animatable<?>> set, Animatable<T> object, T prop, float start, float end, long duration, long startTime, boolean setStartValueAfterStartDelay, Interpolator interpolator) { if (duration <= 0) return; Animation<Animatable<?>> animation = createAnimation(object, prop, start, end, duration, startTime, setStartValueAfterStartDelay, interpolator); set.add(animation); } /** * Helper method to create an {@link ChromeAnimation.AnimatableAnimation} * * @param <T> The Enum type of the Property being used * @param object The object being animated * @param prop The property being animated * @param start The starting value of the animation * @param end The ending value of the animation * @param duration The duration of the animation in ms * @param startTime The start time in ms * @param setStartValueAfterStartDelay See * {@link ChromeAnimation.Animation#setStartValueAfterStartDelay(boolean)} * @param interpolator The interpolator to use for the animation */ public static <T extends Enum<?>> Animation<Animatable<?>> createAnimation( Animatable<T> object, T prop, float start, float end, long duration, long startTime, boolean setStartValueAfterStartDelay, Interpolator interpolator) { Animation<Animatable<?>> animation = new AnimatableAnimation(object, prop, start, end, duration, startTime, interpolator); animation.setStartValueAfterStartDelay(setStartValueAfterStartDelay); return animation; } /** * Checks if the given property is being animated. */ @Override public <V extends Enum<?>> boolean checkProperty(V prop) { return mProperty == prop; } } }