diff options
author | George Mount <mount@google.com> | 2014-08-21 14:28:01 -0700 |
---|---|---|
committer | George Mount <mount@google.com> | 2014-08-29 16:02:28 -0700 |
commit | 984011f6850fd4b6ad4db6d6022bd475d7a2c712 (patch) | |
tree | 1a621beda464056d7671b675ae228b9465f555f5 /core/java/android/animation | |
parent | c9991c7149ccd320200ad62ac0036fe57c5cc831 (diff) | |
download | frameworks_base-984011f6850fd4b6ad4db6d6022bd475d7a2c712.zip frameworks_base-984011f6850fd4b6ad4db6d6022bd475d7a2c712.tar.gz frameworks_base-984011f6850fd4b6ad4db6d6022bd475d7a2c712.tar.bz2 |
Use optimized Keyframes for Path animations.
Bug 17005728
Change-Id: I2e109ed1a3e768e1e0286fc3950516f16509e591
Diffstat (limited to 'core/java/android/animation')
-rw-r--r-- | core/java/android/animation/AnimatorInflater.java | 15 | ||||
-rw-r--r-- | core/java/android/animation/FloatKeyframeSet.java | 9 | ||||
-rw-r--r-- | core/java/android/animation/IntKeyframeSet.java | 9 | ||||
-rw-r--r-- | core/java/android/animation/KeyframeSet.java | 19 | ||||
-rw-r--r-- | core/java/android/animation/Keyframes.java | 94 | ||||
-rw-r--r-- | core/java/android/animation/LayoutTransition.java | 14 | ||||
-rw-r--r-- | core/java/android/animation/ObjectAnimator.java | 41 | ||||
-rw-r--r-- | core/java/android/animation/PathKeyframes.java | 254 | ||||
-rw-r--r-- | core/java/android/animation/PropertyValuesHolder.java | 240 |
9 files changed, 522 insertions, 173 deletions
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index e57be83..f4e4671 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -370,14 +370,23 @@ public class AnimatorInflater { + " propertyXName or propertyYName is needed for PathData"); } else { Path path = PathParser.createPathFromPathData(pathData); - Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, !getFloats); + PathKeyframes keyframeSet = KeyframeSet.ofPath(path); + Keyframes xKeyframes; + Keyframes yKeyframes; + if (getFloats) { + xKeyframes = keyframeSet.createXFloatKeyframes(); + yKeyframes = keyframeSet.createYFloatKeyframes(); + } else { + xKeyframes = keyframeSet.createXIntKeyframes(); + yKeyframes = keyframeSet.createYIntKeyframes(); + } PropertyValuesHolder x = null; PropertyValuesHolder y = null; if (propertyXName != null) { - x = PropertyValuesHolder.ofKeyframe(propertyXName, keyframes[0]); + x = PropertyValuesHolder.ofKeyframes(propertyXName, xKeyframes); } if (propertyYName != null) { - y = PropertyValuesHolder.ofKeyframe(propertyYName, keyframes[1]); + y = PropertyValuesHolder.ofKeyframes(propertyYName, yKeyframes); } if (x == null) { oa.setValues(y); diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java index 2d87e13..12e5862 100644 --- a/core/java/android/animation/FloatKeyframeSet.java +++ b/core/java/android/animation/FloatKeyframeSet.java @@ -30,7 +30,7 @@ import java.util.ArrayList; * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the * Object equivalents of these primitive types.</p> */ -class FloatKeyframeSet extends KeyframeSet { +class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes { private float firstValue; private float lastValue; private float deltaValue; @@ -58,10 +58,11 @@ class FloatKeyframeSet extends KeyframeSet { } @Override - void invalidateCache() { + public void invalidateCache() { firstTime = true; } + @Override public float getFloatValue(float fraction) { if (mNumKeyframes == 2) { if (firstTime) { @@ -135,5 +136,9 @@ class FloatKeyframeSet extends KeyframeSet { return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue(); } + @Override + public Class getType() { + return Float.class; + } } diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java index ce47e2b..7a5b0ec 100644 --- a/core/java/android/animation/IntKeyframeSet.java +++ b/core/java/android/animation/IntKeyframeSet.java @@ -30,7 +30,7 @@ import java.util.ArrayList; * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the * Object equivalents of these primitive types.</p> */ -class IntKeyframeSet extends KeyframeSet { +class IntKeyframeSet extends KeyframeSet implements Keyframes.IntKeyframes { private int firstValue; private int lastValue; private int deltaValue; @@ -58,10 +58,11 @@ class IntKeyframeSet extends KeyframeSet { } @Override - void invalidateCache() { + public void invalidateCache() { firstTime = true; } + @Override public int getIntValue(float fraction) { if (mNumKeyframes == 2) { if (firstTime) { @@ -134,5 +135,9 @@ class IntKeyframeSet extends KeyframeSet { return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue(); } + @Override + public Class getType() { + return Integer.class; + } } diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java index a3db3a1..fc9bbb1 100644 --- a/core/java/android/animation/KeyframeSet.java +++ b/core/java/android/animation/KeyframeSet.java @@ -21,6 +21,7 @@ import java.util.Arrays; import android.animation.Keyframe.IntKeyframe; import android.animation.Keyframe.FloatKeyframe; import android.animation.Keyframe.ObjectKeyframe; +import android.graphics.Path; import android.util.Log; /** @@ -28,7 +29,7 @@ import android.util.Log; * 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 { +class KeyframeSet implements Keyframes { int mNumKeyframes; @@ -52,7 +53,12 @@ class KeyframeSet { * If subclass has variables that it calculates based on the Keyframes, it should reset them * when this method is called because Keyframe contents might have changed. */ - void invalidateCache() { + @Override + public void invalidateCache() { + } + + public ArrayList<Keyframe> getKeyframes() { + return mKeyframes; } public static KeyframeSet ofInt(int... values) { @@ -144,6 +150,10 @@ class KeyframeSet { return new KeyframeSet(keyframes); } + public static PathKeyframes ofPath(Path path) { + return new PathKeyframes(path); + } + /** * Sets the TypeEvaluator to be used when calculating animated values. This object * is required only for KeyframeSets that are not either IntKeyframeSet or FloatKeyframeSet, @@ -157,6 +167,11 @@ class KeyframeSet { } @Override + public Class getType() { + return mFirstKeyframe.getType(); + } + + @Override public KeyframeSet clone() { ArrayList<Keyframe> keyframes = mKeyframes; int numKeyframes = mKeyframes.size(); diff --git a/core/java/android/animation/Keyframes.java b/core/java/android/animation/Keyframes.java new file mode 100644 index 0000000..6611c6c --- /dev/null +++ b/core/java/android/animation/Keyframes.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014 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 interface abstracts a collection of Keyframe objects and is called by + * ValueAnimator to calculate values between those keyframes for a given animation. + */ +interface Keyframes extends Cloneable { + + /** + * Sets the TypeEvaluator to be used when calculating animated values. This object + * is required only for Keyframes that are not either IntKeyframes or FloatKeyframes, + * both of which assume their own evaluator to speed up calculations with those primitive + * types. + * + * @param evaluator The TypeEvaluator to be used to calculate animated values. + */ + void setEvaluator(TypeEvaluator evaluator); + + /** + * @return The value type contained by the contained Keyframes. + */ + Class getType(); + + /** + * 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 + * @return The animated value. + */ + Object getValue(float fraction); + + /** + * If subclass has variables that it calculates based on the Keyframes, it should reset them + * when this method is called because Keyframe contents might have changed. + */ + void invalidateCache(); + + /** + * @return A list of all Keyframes contained by this. This may return null if this is + * not made up of Keyframes. + */ + ArrayList<Keyframe> getKeyframes(); + + Keyframes clone(); + + /** + * A specialization of Keyframes that has integer primitive value calculation. + */ + public interface IntKeyframes extends Keyframes { + + /** + * Works like {@link #getValue(float)}, but returning a primitive. + * @param fraction The elapsed fraction of the animation + * @return The animated value. + */ + int getIntValue(float fraction); + } + + /** + * A specialization of Keyframes that has float primitive value calculation. + */ + public interface FloatKeyframes extends Keyframes { + + /** + * Works like {@link #getValue(float)}, but returning a primitive. + * @param fraction The elapsed fraction of the animation + * @return The animated value. + */ + float getFloatValue(float fraction); + } +} diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java index 188408d..5790682 100644 --- a/core/java/android/animation/LayoutTransition.java +++ b/core/java/android/animation/LayoutTransition.java @@ -899,11 +899,15 @@ public class LayoutTransition { PropertyValuesHolder[] oldValues = valueAnim.getValues(); for (int i = 0; i < oldValues.length; ++i) { PropertyValuesHolder pvh = oldValues[i]; - KeyframeSet keyframeSet = pvh.mKeyframeSet; - if (keyframeSet.mFirstKeyframe == null || - keyframeSet.mLastKeyframe == null || - !keyframeSet.mFirstKeyframe.getValue().equals( - keyframeSet.mLastKeyframe.getValue())) { + if (pvh.mKeyframes instanceof KeyframeSet) { + KeyframeSet keyframeSet = (KeyframeSet) pvh.mKeyframes; + if (keyframeSet.mFirstKeyframe == null || + keyframeSet.mLastKeyframe == null || + !keyframeSet.mFirstKeyframe.getValue().equals( + keyframeSet.mLastKeyframe.getValue())) { + valuesDiffer = true; + } + } else if (!pvh.mKeyframes.getValue(0).equals(pvh.mKeyframes.getValue(1))) { valuesDiffer = true; } } diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java index a4ac73f..500634c 100644 --- a/core/java/android/animation/ObjectAnimator.java +++ b/core/java/android/animation/ObjectAnimator.java @@ -239,9 +239,11 @@ public final class ObjectAnimator extends ValueAnimator { */ public static ObjectAnimator ofInt(Object target, String xPropertyName, String yPropertyName, Path path) { - Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, true); - PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xPropertyName, keyframes[0]); - PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yPropertyName, keyframes[1]); + PathKeyframes keyframes = KeyframeSet.ofPath(path); + PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xPropertyName, + keyframes.createXIntKeyframes()); + PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yPropertyName, + keyframes.createYIntKeyframes()); return ofPropertyValuesHolder(target, x, y); } @@ -278,9 +280,11 @@ public final class ObjectAnimator extends ValueAnimator { */ public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> xProperty, Property<T, Integer> yProperty, Path path) { - Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, true); - PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xProperty, keyframes[0]); - PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yProperty, keyframes[1]); + PathKeyframes keyframes = KeyframeSet.ofPath(path); + PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xProperty, + keyframes.createXIntKeyframes()); + PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yProperty, + keyframes.createYIntKeyframes()); return ofPropertyValuesHolder(target, x, y); } @@ -429,9 +433,11 @@ public final class ObjectAnimator extends ValueAnimator { */ public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName, Path path) { - Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, false); - PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xPropertyName, keyframes[0]); - PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yPropertyName, keyframes[1]); + PathKeyframes keyframes = KeyframeSet.ofPath(path); + PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xPropertyName, + keyframes.createXFloatKeyframes()); + PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yPropertyName, + keyframes.createYFloatKeyframes()); return ofPropertyValuesHolder(target, x, y); } @@ -469,9 +475,11 @@ public final class ObjectAnimator extends ValueAnimator { */ public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> xProperty, Property<T, Float> yProperty, Path path) { - Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, false); - PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xProperty, keyframes[0]); - PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yProperty, keyframes[1]); + PathKeyframes keyframes = KeyframeSet.ofPath(path); + PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xProperty, + keyframes.createXFloatKeyframes()); + PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yProperty, + keyframes.createYFloatKeyframes()); return ofPropertyValuesHolder(target, x, y); } @@ -652,6 +660,10 @@ public final class ObjectAnimator extends ValueAnimator { * uses a type other than <code>PointF</code>, <code>converter</code> can be used to change * from <code>PointF</code> to the type associated with the <code>Property</code>. * + * <p>The PointF passed to <code>converter</code> or <code>property</code>, if + * <code>converter</code> is <code>null</code>, is reused on each animation frame and should + * not be stored by the setter or TypeConverter.</p> + * * @param target The object whose property is to be animated. * @param property The property being animated. Should not be null. * @param converter Converts a PointF to the type associated with the setter. May be @@ -809,10 +821,9 @@ public final class ObjectAnimator extends ValueAnimator { Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration()); for (int i = 0; i < mValues.length; ++i) { PropertyValuesHolder pvh = mValues[i]; - ArrayList<Keyframe> keyframes = pvh.mKeyframeSet.mKeyframes; Log.d(LOG_TAG, " Values[" + i + "]: " + - pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " + - keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue()); + pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " + + pvh.mKeyframes.getValue(1)); } } super.start(); diff --git a/core/java/android/animation/PathKeyframes.java b/core/java/android/animation/PathKeyframes.java new file mode 100644 index 0000000..70eed90 --- /dev/null +++ b/core/java/android/animation/PathKeyframes.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2014 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.graphics.Path; +import android.graphics.PointF; +import android.util.MathUtils; + +import java.util.ArrayList; + +/** + * PathKeyframes relies on approximating the Path as a series of line segments. + * The line segments are recursively divided until there is less than 1/2 pixel error + * between the lines and the curve. Each point of the line segment is converted + * to a Keyframe and a linear interpolation between Keyframes creates a good approximation + * of the curve. + * <p> + * PathKeyframes is optimized to reduce the number of objects created when there are + * many keyframes for a curve. + * </p> + * <p> + * Typically, the returned type is a PointF, but the individual components can be extracted + * as either an IntKeyframes or FloatKeyframes. + * </p> + */ +class PathKeyframes implements Keyframes { + private static final int FRACTION_OFFSET = 0; + private static final int X_OFFSET = 1; + private static final int Y_OFFSET = 2; + private static final int NUM_COMPONENTS = 3; + private static final ArrayList<Keyframe> EMPTY_KEYFRAMES = new ArrayList<Keyframe>(); + + private PointF mTempPointF = new PointF(); + private float[] mKeyframeData; + + public PathKeyframes(Path path) { + this(path, 0.5f); + } + + public PathKeyframes(Path path, float error) { + if (path == null || path.isEmpty()) { + throw new IllegalArgumentException("The path must not be null or empty"); + } + mKeyframeData = path.approximate(error); + } + + @Override + public ArrayList<Keyframe> getKeyframes() { + return EMPTY_KEYFRAMES; + } + + @Override + public Object getValue(float fraction) { + fraction = MathUtils.constrain(fraction, 0, 1); + + int numPoints = mKeyframeData.length / 3; + + if (fraction == 0) { + return pointForIndex(0); + } else if (fraction == 1) { + return pointForIndex(numPoints - 1); + } else { + // Binary search for the correct section + int low = 0; + int high = numPoints - 1; + + while (low <= high) { + int mid = (low + high) / 2; + float midFraction = mKeyframeData[(mid * NUM_COMPONENTS) + FRACTION_OFFSET]; + + if (fraction < midFraction) { + high = mid - 1; + } else if (fraction > midFraction) { + low = mid + 1; + } else { + return pointForIndex(mid); + } + } + + // now high is below the fraction and low is above the fraction + int startBase = (high * NUM_COMPONENTS); + int endBase = (low * NUM_COMPONENTS); + + float startFraction = mKeyframeData[startBase + FRACTION_OFFSET]; + float endFraction = mKeyframeData[endBase + FRACTION_OFFSET]; + + float intervalFraction = (fraction - startFraction)/(endFraction - startFraction); + + float startX = mKeyframeData[startBase + X_OFFSET]; + float endX = mKeyframeData[endBase + X_OFFSET]; + float startY = mKeyframeData[startBase + Y_OFFSET]; + float endY = mKeyframeData[endBase + Y_OFFSET]; + + float x = interpolate(intervalFraction, startX, endX); + float y = interpolate(intervalFraction, startY, endY); + + mTempPointF.set(x, y); + return mTempPointF; + } + } + + @Override + public void invalidateCache() { + } + + @Override + public void setEvaluator(TypeEvaluator evaluator) { + } + + @Override + public Class getType() { + return PointF.class; + } + + @Override + public Keyframes clone() { + Keyframes clone = null; + try { + clone = (Keyframes) super.clone(); + } catch (CloneNotSupportedException e) {} + return clone; + } + + private PointF pointForIndex(int index) { + int base = (index * NUM_COMPONENTS); + int xOffset = base + X_OFFSET; + int yOffset = base + Y_OFFSET; + mTempPointF.set(mKeyframeData[xOffset], mKeyframeData[yOffset]); + return mTempPointF; + } + + private static float interpolate(float fraction, float startValue, float endValue) { + float diff = endValue - startValue; + return startValue + (diff * fraction); + } + + /** + * Returns a FloatKeyframes for the X component of the Path. + * @return a FloatKeyframes for the X component of the Path. + */ + public FloatKeyframes createXFloatKeyframes() { + return new FloatKeyframesBase() { + @Override + public float getFloatValue(float fraction) { + PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); + return pointF.x; + } + }; + } + + /** + * Returns a FloatKeyframes for the Y component of the Path. + * @return a FloatKeyframes for the Y component of the Path. + */ + public FloatKeyframes createYFloatKeyframes() { + return new FloatKeyframesBase() { + @Override + public float getFloatValue(float fraction) { + PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); + return pointF.y; + } + }; + } + + /** + * Returns an IntKeyframes for the X component of the Path. + * @return an IntKeyframes for the X component of the Path. + */ + public IntKeyframes createXIntKeyframes() { + return new IntKeyframesBase() { + @Override + public int getIntValue(float fraction) { + PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); + return Math.round(pointF.x); + } + }; + } + + /** + * Returns an IntKeyframeSet for the Y component of the Path. + * @return an IntKeyframeSet for the Y component of the Path. + */ + public IntKeyframes createYIntKeyframes() { + return new IntKeyframesBase() { + @Override + public int getIntValue(float fraction) { + PointF pointF = (PointF) PathKeyframes.this.getValue(fraction); + return Math.round(pointF.y); + } + }; + } + + private abstract static class SimpleKeyframes implements Keyframes { + @Override + public void setEvaluator(TypeEvaluator evaluator) { + } + + @Override + public void invalidateCache() { + } + + @Override + public ArrayList<Keyframe> getKeyframes() { + return EMPTY_KEYFRAMES; + } + + @Override + public Keyframes clone() { + Keyframes clone = null; + try { + clone = (Keyframes) super.clone(); + } catch (CloneNotSupportedException e) {} + return clone; + } + } + + private abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes { + @Override + public Class getType() { + return Integer.class; + } + + @Override + public Object getValue(float fraction) { + return getIntValue(fraction); + } + } + + private abstract static class FloatKeyframesBase extends SimpleKeyframes + implements FloatKeyframes { + @Override + public Class getType() { + return Float.class; + } + + @Override + public Object getValue(float fraction) { + return getFloatValue(fraction); + } + } +} diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java index 73b83ef..d372933 100644 --- a/core/java/android/animation/PropertyValuesHolder.java +++ b/core/java/android/animation/PropertyValuesHolder.java @@ -18,7 +18,6 @@ package android.animation; import android.graphics.Path; import android.graphics.PointF; -import android.util.FloatMath; import android.util.FloatProperty; import android.util.IntProperty; import android.util.Log; @@ -26,6 +25,7 @@ import android.util.Property; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -75,7 +75,7 @@ public class PropertyValuesHolder implements Cloneable { /** * The set of keyframes (time/value pairs) that define this animation. */ - KeyframeSet mKeyframeSet = null; + Keyframes mKeyframes = null; // type evaluators for the primitive types handled by this implementation @@ -219,11 +219,9 @@ public class PropertyValuesHolder implements Cloneable { * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...) */ public static PropertyValuesHolder ofMultiInt(String propertyName, Path path) { - Keyframe[] keyframes = createKeyframes(path); - KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(keyframes); - TypeEvaluator<PointF> evaluator = new PointFEvaluator(new PointF()); + Keyframes keyframes = KeyframeSet.ofPath(path); PointFToIntArray converter = new PointFToIntArray(); - return new MultiIntValuesHolder(propertyName, converter, evaluator, keyframeSet); + return new MultiIntValuesHolder(propertyName, converter, null, keyframes); } /** @@ -339,11 +337,9 @@ public class PropertyValuesHolder implements Cloneable { * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...) */ public static PropertyValuesHolder ofMultiFloat(String propertyName, Path path) { - Keyframe[] keyframes = createKeyframes(path); - KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(keyframes); - TypeEvaluator<PointF> evaluator = new PointFEvaluator(new PointF()); + Keyframes keyframes = KeyframeSet.ofPath(path); PointFToFloatArray converter = new PointFToFloatArray(); - return new MultiFloatValuesHolder(propertyName, converter, evaluator, keyframeSet); + return new MultiFloatValuesHolder(propertyName, converter, null, keyframes); } /** @@ -415,6 +411,10 @@ public class PropertyValuesHolder implements Cloneable { * <code>TypeConverter</code> to convert from <code>PointF</code> to the target * type. * + * <p>The PointF passed to <code>converter</code> or <code>property</code>, if + * <code>converter</code> is <code>null</code>, is reused on each animation frame and should + * not be stored by the setter or TypeConverter.</p> + * * @param propertyName The name of the property being animated. * @param converter Converts a PointF to the type associated with the setter. May be * null if conversion is unnecessary. @@ -423,9 +423,9 @@ public class PropertyValuesHolder implements Cloneable { */ public static PropertyValuesHolder ofObject(String propertyName, TypeConverter<PointF, ?> converter, Path path) { - Keyframe[] keyframes = createKeyframes(path); - PropertyValuesHolder pvh = ofKeyframe(propertyName, keyframes); - pvh.setEvaluator(new PointFEvaluator(new PointF())); + PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName); + pvh.mKeyframes = KeyframeSet.ofPath(path); + pvh.mValueType = PointF.class; pvh.setConverter(converter); return pvh; } @@ -484,6 +484,10 @@ public class PropertyValuesHolder implements Cloneable { * <code>TypeConverter</code> to convert from <code>PointF</code> to the target * type. * + * <p>The PointF passed to <code>converter</code> or <code>property</code>, if + * <code>converter</code> is <code>null</code>, is reused on each animation frame and should + * not be stored by the setter or TypeConverter.</p> + * * @param property The property being animated. Should not be null. * @param converter Converts a PointF to the type associated with the setter. May be * null if conversion is unnecessary. @@ -492,9 +496,9 @@ public class PropertyValuesHolder implements Cloneable { */ public static <V> PropertyValuesHolder ofObject(Property<?, V> property, TypeConverter<PointF, V> converter, Path path) { - Keyframe[] keyframes = createKeyframes(path); - PropertyValuesHolder pvh = ofKeyframe(property, keyframes); - pvh.setEvaluator(new PointFEvaluator(new PointF())); + PropertyValuesHolder pvh = new PropertyValuesHolder(property); + pvh.mKeyframes = KeyframeSet.ofPath(path); + pvh.mValueType = PointF.class; pvh.setConverter(converter); return pvh; } @@ -520,17 +524,7 @@ public class PropertyValuesHolder implements Cloneable { */ public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) { KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); - if (keyframeSet instanceof IntKeyframeSet) { - return new IntPropertyValuesHolder(propertyName, (IntKeyframeSet) keyframeSet); - } else if (keyframeSet instanceof FloatKeyframeSet) { - return new FloatPropertyValuesHolder(propertyName, (FloatKeyframeSet) keyframeSet); - } - else { - PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName); - pvh.mKeyframeSet = keyframeSet; - pvh.mValueType = ((Keyframe)values[0]).getType(); - return pvh; - } + return ofKeyframes(propertyName, keyframeSet); } /** @@ -551,15 +545,32 @@ public class PropertyValuesHolder implements Cloneable { */ public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) { KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); - if (keyframeSet instanceof IntKeyframeSet) { - return new IntPropertyValuesHolder(property, (IntKeyframeSet) keyframeSet); - } else if (keyframeSet instanceof FloatKeyframeSet) { - return new FloatPropertyValuesHolder(property, (FloatKeyframeSet) keyframeSet); + return ofKeyframes(property, keyframeSet); + } + + static PropertyValuesHolder ofKeyframes(String propertyName, Keyframes keyframes) { + if (keyframes instanceof Keyframes.IntKeyframes) { + return new IntPropertyValuesHolder(propertyName, (Keyframes.IntKeyframes) keyframes); + } else if (keyframes instanceof Keyframes.FloatKeyframes) { + return new FloatPropertyValuesHolder(propertyName, + (Keyframes.FloatKeyframes) keyframes); + } else { + PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName); + pvh.mKeyframes = keyframes; + pvh.mValueType = keyframes.getType(); + return pvh; } - else { + } + + static PropertyValuesHolder ofKeyframes(Property property, Keyframes keyframes) { + if (keyframes instanceof Keyframes.IntKeyframes) { + return new IntPropertyValuesHolder(property, (Keyframes.IntKeyframes) keyframes); + } else if (keyframes instanceof Keyframes.FloatKeyframes) { + return new FloatPropertyValuesHolder(property, (Keyframes.FloatKeyframes) keyframes); + } else { PropertyValuesHolder pvh = new PropertyValuesHolder(property); - pvh.mKeyframeSet = keyframeSet; - pvh.mValueType = ((Keyframe)values[0]).getType(); + pvh.mKeyframes = keyframes; + pvh.mValueType = keyframes.getType(); return pvh; } } @@ -579,7 +590,7 @@ public class PropertyValuesHolder implements Cloneable { */ public void setIntValues(int... values) { mValueType = int.class; - mKeyframeSet = KeyframeSet.ofInt(values); + mKeyframes = KeyframeSet.ofInt(values); } /** @@ -597,7 +608,7 @@ public class PropertyValuesHolder implements Cloneable { */ public void setFloatValues(float... values) { mValueType = float.class; - mKeyframeSet = KeyframeSet.ofFloat(values); + mKeyframes = KeyframeSet.ofFloat(values); } /** @@ -612,7 +623,7 @@ public class PropertyValuesHolder implements Cloneable { for (int i = 0; i < numKeyframes; ++i) { keyframes[i] = (Keyframe)values[i]; } - mKeyframeSet = new KeyframeSet(keyframes); + mKeyframes = new KeyframeSet(keyframes); } /** @@ -630,9 +641,9 @@ public class PropertyValuesHolder implements Cloneable { */ public void setObjectValues(Object... values) { mValueType = values[0].getClass(); - mKeyframeSet = KeyframeSet.ofObject(values); + mKeyframes = KeyframeSet.ofObject(values); if (mEvaluator != null) { - mKeyframeSet.setEvaluator(mEvaluator); + mKeyframes.setEvaluator(mEvaluator); } } @@ -775,12 +786,15 @@ public class PropertyValuesHolder implements Cloneable { * @param target The object on which the setter (and possibly getter) exist. */ void setupSetterAndGetter(Object target) { - mKeyframeSet.invalidateCache(); + mKeyframes.invalidateCache(); if (mProperty != null) { // check to make sure that mProperty is on the class of target try { Object testValue = null; - for (Keyframe kf : mKeyframeSet.mKeyframes) { + ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes(); + int keyframeCount = keyframes == null ? 0 : keyframes.size(); + for (int i = 0; i < keyframeCount; i++) { + Keyframe kf = keyframes.get(i); if (!kf.hasValue() || kf.valueWasSetOnStart()) { if (testValue == null) { testValue = convertBack(mProperty.get(target)); @@ -800,7 +814,10 @@ public class PropertyValuesHolder implements Cloneable { if (mSetter == null) { setupSetter(targetClass); } - for (Keyframe kf : mKeyframeSet.mKeyframes) { + ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes(); + int keyframeCount = keyframes == null ? 0 : keyframes.size(); + for (int i = 0; i < keyframeCount; i++) { + Keyframe kf = keyframes.get(i); if (!kf.hasValue() || kf.valueWasSetOnStart()) { if (mGetter == null) { setupGetter(targetClass); @@ -873,7 +890,10 @@ public class PropertyValuesHolder implements Cloneable { * @param target The object which holds the start values that should be set. */ void setupStartValue(Object target) { - setupValue(target, mKeyframeSet.mKeyframes.get(0)); + ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes(); + if (!keyframes.isEmpty()) { + setupValue(target, keyframes.get(0)); + } } /** @@ -885,7 +905,10 @@ public class PropertyValuesHolder implements Cloneable { * @param target The object which holds the start values that should be set. */ void setupEndValue(Object target) { - setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1)); + ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes(); + if (!keyframes.isEmpty()) { + setupValue(target, keyframes.get(keyframes.size() - 1)); + } } @Override @@ -894,7 +917,7 @@ public class PropertyValuesHolder implements Cloneable { PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone(); newPVH.mPropertyName = mPropertyName; newPVH.mProperty = mProperty; - newPVH.mKeyframeSet = mKeyframeSet.clone(); + newPVH.mKeyframes = mKeyframes.clone(); newPVH.mEvaluator = mEvaluator; return newPVH; } catch (CloneNotSupportedException e) { @@ -941,7 +964,7 @@ public class PropertyValuesHolder implements Cloneable { if (mEvaluator != null) { // KeyframeSet knows how to evaluate the common types - only give it a custom // evaluator if one has been set on this class - mKeyframeSet.setEvaluator(mEvaluator); + mKeyframes.setEvaluator(mEvaluator); } } @@ -957,7 +980,7 @@ public class PropertyValuesHolder implements Cloneable { */ public void setEvaluator(TypeEvaluator evaluator) { mEvaluator = evaluator; - mKeyframeSet.setEvaluator(evaluator); + mKeyframes.setEvaluator(evaluator); } /** @@ -967,7 +990,7 @@ public class PropertyValuesHolder implements Cloneable { * @param fraction The elapsed, interpolated fraction of the animation. */ void calculateValue(float fraction) { - Object value = mKeyframeSet.getValue(fraction); + Object value = mKeyframes.getValue(fraction); mAnimatedValue = mConverter == null ? value : mConverter.convert(value); } @@ -1025,7 +1048,7 @@ public class PropertyValuesHolder implements Cloneable { @Override public String toString() { - return mPropertyName + ": " + mKeyframeSet.toString(); + return mPropertyName + ": " + mKeyframes.toString(); } /** @@ -1059,21 +1082,21 @@ public class PropertyValuesHolder implements Cloneable { long mJniSetter; private IntProperty mIntProperty; - IntKeyframeSet mIntKeyframeSet; + Keyframes.IntKeyframes mIntKeyframes; int mIntAnimatedValue; - public IntPropertyValuesHolder(String propertyName, IntKeyframeSet keyframeSet) { + public IntPropertyValuesHolder(String propertyName, Keyframes.IntKeyframes keyframes) { super(propertyName); mValueType = int.class; - mKeyframeSet = keyframeSet; - mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; + mKeyframes = keyframes; + mIntKeyframes = keyframes; } - public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) { + public IntPropertyValuesHolder(Property property, Keyframes.IntKeyframes keyframes) { super(property); mValueType = int.class; - mKeyframeSet = keyframeSet; - mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; + mKeyframes = keyframes; + mIntKeyframes = keyframes; if (property instanceof IntProperty) { mIntProperty = (IntProperty) mProperty; } @@ -1095,12 +1118,12 @@ public class PropertyValuesHolder implements Cloneable { @Override public void setIntValues(int... values) { super.setIntValues(values); - mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; + mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes; } @Override void calculateValue(float fraction) { - mIntAnimatedValue = mIntKeyframeSet.getIntValue(fraction); + mIntAnimatedValue = mIntKeyframes.getIntValue(fraction); } @Override @@ -1111,7 +1134,7 @@ public class PropertyValuesHolder implements Cloneable { @Override public IntPropertyValuesHolder clone() { IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone(); - newPVH.mIntKeyframeSet = (IntKeyframeSet) newPVH.mKeyframeSet; + newPVH.mIntKeyframes = (Keyframes.IntKeyframes) newPVH.mKeyframes; return newPVH; } @@ -1196,21 +1219,21 @@ public class PropertyValuesHolder implements Cloneable { long mJniSetter; private FloatProperty mFloatProperty; - FloatKeyframeSet mFloatKeyframeSet; + Keyframes.FloatKeyframes mFloatKeyframes; float mFloatAnimatedValue; - public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) { + public FloatPropertyValuesHolder(String propertyName, Keyframes.FloatKeyframes keyframes) { super(propertyName); mValueType = float.class; - mKeyframeSet = keyframeSet; - mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; + mKeyframes = keyframes; + mFloatKeyframes = keyframes; } - public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) { + public FloatPropertyValuesHolder(Property property, Keyframes.FloatKeyframes keyframes) { super(property); mValueType = float.class; - mKeyframeSet = keyframeSet; - mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; + mKeyframes = keyframes; + mFloatKeyframes = keyframes; if (property instanceof FloatProperty) { mFloatProperty = (FloatProperty) mProperty; } @@ -1232,12 +1255,12 @@ public class PropertyValuesHolder implements Cloneable { @Override public void setFloatValues(float... values) { super.setFloatValues(values); - mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; + mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes; } @Override void calculateValue(float fraction) { - mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction); + mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction); } @Override @@ -1248,7 +1271,7 @@ public class PropertyValuesHolder implements Cloneable { @Override public FloatPropertyValuesHolder clone() { FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone(); - newPVH.mFloatKeyframeSet = (FloatKeyframeSet) newPVH.mKeyframeSet; + newPVH.mFloatKeyframes = (Keyframes.FloatKeyframes) newPVH.mKeyframes; return newPVH; } @@ -1340,10 +1363,10 @@ public class PropertyValuesHolder implements Cloneable { } public MultiFloatValuesHolder(String propertyName, TypeConverter converter, - TypeEvaluator evaluator, KeyframeSet keyframeSet) { + TypeEvaluator evaluator, Keyframes keyframes) { super(propertyName); setConverter(converter); - mKeyframeSet = keyframeSet; + mKeyframes = keyframes; setEvaluator(evaluator); } @@ -1443,10 +1466,10 @@ public class PropertyValuesHolder implements Cloneable { } public MultiIntValuesHolder(String propertyName, TypeConverter converter, - TypeEvaluator evaluator, KeyframeSet keyframeSet) { + TypeEvaluator evaluator, Keyframes keyframes) { super(propertyName); setConverter(converter); - mKeyframeSet = keyframeSet; + mKeyframes = keyframes; setEvaluator(evaluator); } @@ -1532,77 +1555,6 @@ public class PropertyValuesHolder implements Cloneable { } } - /* Path interpolation relies on approximating the Path as a series of line segments. - The line segments are recursively divided until there is less than 1/2 pixel error - between the lines and the curve. Each point of the line segment is converted - to a Keyframe and a linear interpolation between Keyframes creates a good approximation - of the curve. - - The fraction for each Keyframe is the length along the Path to the point, divided by - the total Path length. Two points may have the same fraction in the case of a move - command causing a disjoint Path. - - The value for each Keyframe is either the point as a PointF or one of the x or y - coordinates as an int or float. In the latter case, two Keyframes are generated for - each point that have the same fraction. */ - - /** - * Returns separate Keyframes arrays for the x and y coordinates along a Path. If - * isInt is true, the Keyframes will be IntKeyframes, otherwise they will be FloatKeyframes. - * The element at index 0 are the x coordinate Keyframes and element at index 1 are the - * y coordinate Keyframes. The returned values can be linearly interpolated and get less - * than 1/2 pixel error. - */ - static Keyframe[][] createKeyframes(Path path, boolean isInt) { - if (path == null || path.isEmpty()) { - throw new IllegalArgumentException("The path must not be null or empty"); - } - float[] pointComponents = path.approximate(0.5f); - - int numPoints = pointComponents.length / 3; - - Keyframe[][] keyframes = new Keyframe[2][]; - keyframes[0] = new Keyframe[numPoints]; - keyframes[1] = new Keyframe[numPoints]; - int componentIndex = 0; - for (int i = 0; i < numPoints; i++) { - float fraction = pointComponents[componentIndex++]; - float x = pointComponents[componentIndex++]; - float y = pointComponents[componentIndex++]; - if (isInt) { - keyframes[0][i] = Keyframe.ofInt(fraction, Math.round(x)); - keyframes[1][i] = Keyframe.ofInt(fraction, Math.round(y)); - } else { - keyframes[0][i] = Keyframe.ofFloat(fraction, x); - keyframes[1][i] = Keyframe.ofFloat(fraction, y); - } - } - return keyframes; - } - - /** - * Returns PointF Keyframes for a Path. The resulting points can be linearly interpolated - * with less than 1/2 pixel in error. - */ - private static Keyframe[] createKeyframes(Path path) { - if (path == null || path.isEmpty()) { - throw new IllegalArgumentException("The path must not be null or empty"); - } - float[] pointComponents = path.approximate(0.5f); - - int numPoints = pointComponents.length / 3; - - Keyframe[] keyframes = new Keyframe[numPoints]; - int componentIndex = 0; - for (int i = 0; i < numPoints; i++) { - float fraction = pointComponents[componentIndex++]; - float x = pointComponents[componentIndex++]; - float y = pointComponents[componentIndex++]; - keyframes[i] = Keyframe.ofObject(fraction, new PointF(x, y)); - } - return keyframes; - } - /** * Convert from PointF to float[] for multi-float setters along a Path. */ |