summaryrefslogtreecommitdiffstats
path: root/core/java/android/animation
diff options
context:
space:
mode:
authorGeorge Mount <mount@google.com>2014-08-21 14:28:01 -0700
committerGeorge Mount <mount@google.com>2014-08-29 16:02:28 -0700
commit984011f6850fd4b6ad4db6d6022bd475d7a2c712 (patch)
tree1a621beda464056d7671b675ae228b9465f555f5 /core/java/android/animation
parentc9991c7149ccd320200ad62ac0036fe57c5cc831 (diff)
downloadframeworks_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.java15
-rw-r--r--core/java/android/animation/FloatKeyframeSet.java9
-rw-r--r--core/java/android/animation/IntKeyframeSet.java9
-rw-r--r--core/java/android/animation/KeyframeSet.java19
-rw-r--r--core/java/android/animation/Keyframes.java94
-rw-r--r--core/java/android/animation/LayoutTransition.java14
-rw-r--r--core/java/android/animation/ObjectAnimator.java41
-rw-r--r--core/java/android/animation/PathKeyframes.java254
-rw-r--r--core/java/android/animation/PropertyValuesHolder.java240
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.
*/