diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index db8b9eba5fb8e..24c3c88afdea5 100755 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -189,6 +189,11 @@ public class Animator extends Animatable { */ private Object mAnimatedValue = null; + /** + * The set of keyframes (time/value pairs) that define this animation. + */ + private KeyframeSet mKeyframeSet = null; + /** * The type of the values, as determined by the valueFrom/valueTo properties. */ @@ -221,6 +226,20 @@ public class Animator extends Animatable { this.mValueType = valueType; } + /** + * This constructor takes a set of {@link Keyframe} objects that define the values + * for the animation, along with the times at which those values will hold true during + * the animation. + * + * @param duration The length of the animation, in milliseconds. + * @param keyframes The set of keyframes that define the time/value pairs for the animation. + */ + public Animator(long duration, Keyframe...keyframes) { + mDuration = duration; + mKeyframeSet = new KeyframeSet(keyframes); + mValueType = keyframes[0].getType(); + } + /** * This function is called immediately before processing the first animation * frame of an animation. If there is a nonzero startDelay, the @@ -420,6 +439,15 @@ public class Animator extends Animatable { return sFrameDelay; } + /** + * Gets the set of keyframes that define this animation. + * + * @return KeyframeSet The set of keyframes for this animation. + */ + KeyframeSet getKeyframes() { + return mKeyframeSet; + } + /** * Gets the value that this animation will start from. * @@ -751,7 +779,11 @@ public class Animator extends Animatable { */ void animateValue(float fraction) { fraction = mInterpolator.getInterpolation(fraction); - mAnimatedValue = mEvaluator.evaluate(fraction, mValueFrom, mValueTo); + if (mKeyframeSet != null) { + mAnimatedValue = mKeyframeSet.getValue(fraction, mEvaluator); + } else { + mAnimatedValue = mEvaluator.evaluate(fraction, mValueFrom, mValueTo); + } if (mUpdateListeners != null) { int numListeners = mUpdateListeners.size(); for (int i = 0; i < numListeners; ++i) { diff --git a/core/java/android/animation/Keyframe.java b/core/java/android/animation/Keyframe.java new file mode 100644 index 0000000000000..b98994a37440e --- /dev/null +++ b/core/java/android/animation/Keyframe.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.animation; + +import android.view.animation.Interpolator; + +/** + * This class represents timea time/value pair for an animation. The Keyframe class is used + * by {@link Animator} to define the values that the animation target will have over the course + * of the animation. As the time proceeds from one keyframe to the other, the value of the + * target object will animate between the value at the previous keyframe and the value at the + * next keyframe. Each keyframe also holds an option {@link android.view.animation.Interpolator} + * object, which defines the time interpolation over the intervalue preceding the keyframe. + */ +public class Keyframe { + /** + * The time at which mValue will hold true. + */ + private float mFraction; + + /** + * The value of the animation at the time mFraction. + */ + private Object mValue; + + /** + * The type of the value in this Keyframe. This type is determined at construction time, + * based on the type of the value object passed into the constructor. + */ + private Class mValueType; + + /** + * The optional time interpolator for the interval preceding this keyframe. A null interpolator + * (the default) results in linear interpolation over the interval. + */ + private Interpolator mInterpolator = null; + + /** + * Private constructor, called from the public constructors with the additional + * valueType parameter. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + * @param value The value that the object will animate to as the animation time approaches + * the time in this keyframe, and the the value animated from as the time passes the time in + * this keyframe. + * @param valueType The type of the value object. This is used by the + * {@link #getValue()} functionm, which is queried by {@link Animator} to determine + * the type of {@link TypeEvaluator} to use to interpolate between values. + */ + private Keyframe(float fraction, Object value, Class valueType) { + mFraction = fraction; + mValue = value; + mValueType = valueType; + } + + /** + * Constructs a Keyframe object with the given time and value. The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + * @param value The value that the object will animate to as the animation time approaches + * the time in this keyframe, and the the value animated from as the time passes the time in + * this keyframe. + */ + public Keyframe(float fraction, Object value) { + this(fraction, value, Object.class); + } + + /** + * Constructs a Keyframe object with the given time and integer value. The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + * @param value The value that the object will animate to as the animation time approaches + * the time in this keyframe, and the the value animated from as the time passes the time in + * this keyframe. + */ + public Keyframe(float fraction, int value) { + this(fraction, value, int.class); + } + + /** + * Constructs a Keyframe object with the given time and float value. The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + * @param value The value that the object will animate to as the animation time approaches + * the time in this keyframe, and the the value animated from as the time passes the time in + * this keyframe. + */ + public Keyframe(float fraction, float value) { + this(fraction, value, float.class); + } + + /** + * Constructs a Keyframe object with the given time and double value. The time defines the + * time, as a proportion of an overall animation's duration, at which the value will hold true + * for the animation. The value for the animation between keyframes will be calculated as + * an interpolation between the values at those keyframes. + * + * @param fraction The time, expressed as a value between 0 and 1, representing the fraction + * of time elapsed of the overall animation duration. + * @param value The value that the object will animate to as the animation time approaches + * the time in this keyframe, and the the value animated from as the time passes the time in + * this keyframe. + */ + public Keyframe(float fraction, double value) { + this(fraction, value, double.class); + } + + /** + * Gets the value for this Keyframe. + * + * @return The value for this Keyframe. + */ + public Object getValue() { + return mValue; + } + + /** + * Gets the time for this keyframe, as a fraction of the overall animation duration. + * + * @return The time associated with this keyframe, as a fraction of the overall animation + * duration. This should be a value between 0 and 1. + */ + public float getFraction() { + return mFraction; + } + + /** + * Gets the optional interpolator for this Keyframe. A value of null indicates + * that there is no interpolation, which is the same as linear interpolation. + * + * @return The optional interpolator for this Keyframe. + */ + public Interpolator getInterpolator() { + return mInterpolator; + } + + /** + * Sets the optional interpolator for this Keyframe. A value of null indicates + * that there is no interpolation, which is the same as linear interpolation. + * + * @return The optional interpolator for this Keyframe. + */ + public void setInterpolator(Interpolator interpolator) { + mInterpolator = interpolator; + } + + /** + * Gets the type of keyframe. This information is used by Animator to determine the type of + * {@linke TypeEvaluator} to use when calculating values between keyframes. The type is based + * on the type of Keyframe created. For example, {@link IntKeyframe} returns a value of + * int.class. This superclass returns a value of Object.class. + * + * @return The type of the value stored in the Keyframe. + */ + public Class getType() { + return mValueType; + } +} diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java new file mode 100644 index 0000000000000..59e9708e3deb5 --- /dev/null +++ b/core/java/android/animation/KeyframeSet.java @@ -0,0 +1,99 @@ +/* + * 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 android.view.animation.Interpolator; + +/** + * This class holds a collection of Keyframe objects and is called by Animator to calculate + * values between those keyframes for a given animation. The class internal to the animation + * package because it is an implementation detail of how Keyframes are stored and used. + */ +class KeyframeSet { + + private int mNumKeyframes; + + private ArrayList mKeyframes; + + public KeyframeSet(Keyframe... keyframes) { + mKeyframes = new ArrayList(); + for (Keyframe keyframe : keyframes) { + mKeyframes.add(keyframe); + } + mNumKeyframes = mKeyframes.size(); + } + + /** + * Gets the animated value, given the elapsed fraction of the animation (interpolated by the + * animation's interpolator) and the evaluator used to calculate in-between values. This + * function maps the input fraction to the appropriate keyframe interval and a fraction + * between them and returns the interpolated value. Note that the input fraction may fall + * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a + * spring interpolation that might send the fraction past 1.0). We handle this situation by + * just using the two keyframes at the appropriate end when the value is outside those bounds. + * + * @param fraction The elapsed fraction of the animation + * @param evaluator The type evaluator to use when calculating the interpolated values. + * @return The animated value. + */ + public Object getValue(float fraction, TypeEvaluator evaluator) { + // TODO: special-case 2-keyframe common case + + if (fraction <= 0f) { + final Keyframe prevKeyframe = mKeyframes.get(0); + final Keyframe nextKeyframe = mKeyframes.get(1); + final Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevKeyframe.getFraction()) / + (nextKeyframe.getFraction() - prevKeyframe.getFraction()); + return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(), + nextKeyframe.getValue()); + } else if (fraction >= 1f) { + final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2); + final Keyframe nextKeyframe = mKeyframes.get(mNumKeyframes - 1); + final Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevKeyframe.getFraction()) / + (nextKeyframe.getFraction() - prevKeyframe.getFraction()); + return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(), + nextKeyframe.getValue()); + } + Keyframe prevKeyframe = mKeyframes.get(0); + for (int i = 1; i < mNumKeyframes; ++i) { + Keyframe nextKeyframe = mKeyframes.get(i); + if (fraction < nextKeyframe.getFraction()) { + final Interpolator interpolator = nextKeyframe.getInterpolator(); + if (interpolator != null) { + fraction = interpolator.getInterpolation(fraction); + } + float intervalFraction = (fraction - prevKeyframe.getFraction()) / + (nextKeyframe.getFraction() - prevKeyframe.getFraction()); + return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(), + nextKeyframe.getValue()); + } + prevKeyframe = nextKeyframe; + } + // shouldn't get here + return mKeyframes.get(mNumKeyframes - 1).getValue(); + } +} diff --git a/core/java/android/animation/PropertyAnimator.java b/core/java/android/animation/PropertyAnimator.java index 99799f0b747a5..937dd586441fd 100644 --- a/core/java/android/animation/PropertyAnimator.java +++ b/core/java/android/animation/PropertyAnimator.java @@ -55,6 +55,9 @@ public final class PropertyAnimator extends Animator { // at a time. private ReentrantReadWriteLock propertyMapLock = new ReentrantReadWriteLock(); + // Used to pass single value to varargs parameter in setter invocation + private Object[] mTmpValueArray = new Object[1]; + /** * Sets the name of the property that will be animated. This name is used to derive @@ -271,6 +274,33 @@ public final class PropertyAnimator extends Animator { mPropertyName = propertyName; } + /** + * A constructor that takes Keyframes. When this constructor + * is called, the system expects to find a setter for propertyName on + * the target object that takes a value of the same type as that returned from + * {@link Keyframe#getType()}. + * . + * + * @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 setName(), where name is + * the name of the property passed in as the propertyName parameter. + * @param propertyName The name of the property on the target object + * that will be animated. Given this name, the constructor will search for a + * setter on the target object with the name setPropertyName. For example, + * if the constructor is called with propertyName = "foo", then the + * target object should have a setter function with the name setFoo(). + * @param keyframes The set of keyframes that define the times and values for the animation. + * These keyframes should be ordered in increasing time value, should have a starting + * keyframe with a fraction of 0 and and ending keyframe with a fraction of 1. + */ + public PropertyAnimator(int duration, Object target, String propertyName, + Keyframe...keyframes) { + super(duration, keyframes); + mTarget = target; + mPropertyName = propertyName; + } + /** * This function is called immediately before processing the first animation * frame of an animation. If there is a nonzero startDelay, the @@ -309,7 +339,7 @@ public final class PropertyAnimator extends Animator { propertyMapLock.writeLock().unlock(); } } - if (getValueFrom() == null || getValueTo() == null) { + if (getKeyframes() == null && (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) { @@ -376,7 +406,8 @@ public final class PropertyAnimator extends Animator { super.animateValue(fraction); if (mSetter != null) { try { - mSetter.invoke(mTarget, getAnimatedValue()); + mTmpValueArray[0] = getAnimatedValue(); + mSetter.invoke(mTarget, mTmpValueArray); } catch (InvocationTargetException e) { Log.e("PropertyAnimator", e.toString()); } catch (IllegalAccessException e) {