diff options
-rw-r--r-- | api/current.txt | 18 | ||||
-rw-r--r-- | core/java/android/animation/ObjectAnimator.java | 180 | ||||
-rw-r--r-- | core/java/android/animation/PointFEvaluator.java | 83 | ||||
-rw-r--r-- | core/java/android/animation/PropertyValuesHolder.java | 202 | ||||
-rw-r--r-- | core/jni/android/graphics/Path.cpp | 195 | ||||
-rw-r--r-- | graphics/java/android/graphics/Path.java | 23 |
6 files changed, 693 insertions, 8 deletions
diff --git a/api/current.txt b/api/current.txt index 2845af1..4d500c6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2537,22 +2537,36 @@ package android.animation { method public static android.animation.ObjectAnimator ofArgb(java.lang.Object, java.lang.String, int...); method public static android.animation.ObjectAnimator ofArgb(T, android.util.Property<T, java.lang.Integer>, int...); method public static android.animation.ObjectAnimator ofFloat(java.lang.Object, java.lang.String, float...); + method public static android.animation.ObjectAnimator ofFloat(java.lang.Object, java.lang.String, java.lang.String, android.graphics.Path); method public static android.animation.ObjectAnimator ofFloat(T, android.util.Property<T, java.lang.Float>, float...); + method public static android.animation.ObjectAnimator ofFloat(T, android.util.Property<T, java.lang.Float>, android.util.Property<T, java.lang.Float>, android.graphics.Path); method public static android.animation.ObjectAnimator ofInt(java.lang.Object, java.lang.String, int...); + method public static android.animation.ObjectAnimator ofInt(java.lang.Object, java.lang.String, java.lang.String, android.graphics.Path); method public static android.animation.ObjectAnimator ofInt(T, android.util.Property<T, java.lang.Integer>, int...); + method public static android.animation.ObjectAnimator ofInt(T, android.util.Property<T, java.lang.Integer>, android.util.Property<T, java.lang.Integer>, android.graphics.Path); method public static android.animation.ObjectAnimator ofMultiFloat(java.lang.Object, java.lang.String, float[][]); + method public static android.animation.ObjectAnimator ofMultiFloat(java.lang.Object, java.lang.String, android.graphics.Path); method public static android.animation.ObjectAnimator ofMultiFloat(java.lang.Object, java.lang.String, android.animation.TypeConverter<T, float[]>, android.animation.TypeEvaluator<T>, T...); method public static android.animation.ObjectAnimator ofMultiInt(java.lang.Object, java.lang.String, int[][]); + method public static android.animation.ObjectAnimator ofMultiInt(java.lang.Object, java.lang.String, android.graphics.Path); method public static android.animation.ObjectAnimator ofMultiInt(java.lang.Object, java.lang.String, android.animation.TypeConverter<T, int[]>, android.animation.TypeEvaluator<T>, T...); method public static android.animation.ObjectAnimator ofObject(java.lang.Object, java.lang.String, android.animation.TypeEvaluator, java.lang.Object...); + method public static android.animation.ObjectAnimator ofObject(java.lang.Object, java.lang.String, android.animation.TypeConverter<android.graphics.PointF, ?>, android.graphics.Path); method public static android.animation.ObjectAnimator ofObject(T, android.util.Property<T, V>, android.animation.TypeEvaluator<V>, V...); method public static android.animation.ObjectAnimator ofObject(T, android.util.Property<T, P>, android.animation.TypeConverter<V, P>, android.animation.TypeEvaluator<V>, V...); + method public static android.animation.ObjectAnimator ofObject(T, android.util.Property<T, V>, android.animation.TypeConverter<android.graphics.PointF, V>, android.graphics.Path); method public static android.animation.ObjectAnimator ofPropertyValuesHolder(java.lang.Object, android.animation.PropertyValuesHolder...); method public void setAutoCancel(boolean); method public void setProperty(android.util.Property); method public void setPropertyName(java.lang.String); } + public class PointFEvaluator implements android.animation.TypeEvaluator { + ctor public PointFEvaluator(); + ctor public PointFEvaluator(android.graphics.PointF); + method public android.graphics.PointF evaluate(float, android.graphics.PointF, android.graphics.PointF); + } + public class PropertyValuesHolder implements java.lang.Cloneable { method public android.animation.PropertyValuesHolder clone(); method public java.lang.String getPropertyName(); @@ -2563,14 +2577,18 @@ package android.animation { method public static android.animation.PropertyValuesHolder ofKeyframe(java.lang.String, android.animation.Keyframe...); method public static android.animation.PropertyValuesHolder ofKeyframe(android.util.Property, android.animation.Keyframe...); method public static android.animation.PropertyValuesHolder ofMultiFloat(java.lang.String, float[][]); + method public static android.animation.PropertyValuesHolder ofMultiFloat(java.lang.String, android.graphics.Path); method public static android.animation.PropertyValuesHolder ofMultiFloat(java.lang.String, android.animation.TypeConverter<V, float[]>, android.animation.TypeEvaluator<V>, V...); method public static android.animation.PropertyValuesHolder ofMultiFloat(java.lang.String, android.animation.TypeConverter<T, float[]>, android.animation.TypeEvaluator<T>, android.animation.Keyframe...); method public static android.animation.PropertyValuesHolder ofMultiInt(java.lang.String, int[][]); + method public static android.animation.PropertyValuesHolder ofMultiInt(java.lang.String, android.graphics.Path); method public static android.animation.PropertyValuesHolder ofMultiInt(java.lang.String, android.animation.TypeConverter<V, int[]>, android.animation.TypeEvaluator<V>, V...); method public static android.animation.PropertyValuesHolder ofMultiInt(java.lang.String, android.animation.TypeConverter<T, int[]>, android.animation.TypeEvaluator<T>, android.animation.Keyframe...); method public static android.animation.PropertyValuesHolder ofObject(java.lang.String, android.animation.TypeEvaluator, java.lang.Object...); + method public static android.animation.PropertyValuesHolder ofObject(java.lang.String, android.animation.TypeConverter<android.graphics.PointF, ?>, android.graphics.Path); method public static android.animation.PropertyValuesHolder ofObject(android.util.Property, android.animation.TypeEvaluator<V>, V...); method public static android.animation.PropertyValuesHolder ofObject(android.util.Property<?, V>, android.animation.TypeConverter<T, V>, android.animation.TypeEvaluator<T>, T...); + method public static android.animation.PropertyValuesHolder ofObject(android.util.Property<?, V>, android.animation.TypeConverter<android.graphics.PointF, V>, android.graphics.Path); method public void setConverter(android.animation.TypeConverter); method public void setEvaluator(android.animation.TypeEvaluator); method public void setFloatValues(float...); diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java index 3adbf08..c0ce795 100644 --- a/core/java/android/animation/ObjectAnimator.java +++ b/core/java/android/animation/ObjectAnimator.java @@ -16,6 +16,8 @@ package android.animation; +import android.graphics.Path; +import android.graphics.PointF; import android.util.Log; import android.util.Property; @@ -210,6 +212,31 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code> + * using two properties. A <code>Path</code></> animation moves in two dimensions, animating + * coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are integers that are set to separate properties designated by + * <code>xPropertyName</code> and <code>yPropertyName</code>. + * + * @param target The object whose properties are to be animated. This object should + * have public methods on it called <code>setNameX()</code> and + * <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code> + * are the value of <code>xPropertyName</code> and <code>yPropertyName</code> + * parameters, respectively. + * @param xPropertyName The name of the property for the x coordinate being animated. + * @param yPropertyName The name of the property for the y coordinate being animated. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + 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]); + return ofPropertyValuesHolder(target, x, y); + } + + /** * Constructs and returns an ObjectAnimator that animates between int values. A single * value implies that that value is the one being animated to. Two values imply starting * and ending values. More than two values imply a starting value, values to animate through @@ -228,6 +255,27 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code> + * using two properties. A <code>Path</code></> animation moves in two dimensions, animating + * coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are integers that are set to separate properties, <code>xProperty</code> and + * <code>yProperty</code>. + * + * @param target The object whose properties are to be animated. + * @param xProperty The property for the x coordinate being animated. + * @param yProperty The property for the y coordinate being animated. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + 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]); + return ofPropertyValuesHolder(target, x, y); + } + + /** * Constructs and returns an ObjectAnimator that animates over int values for a multiple * parameters setter. Only public methods that take only int parameters are supported. * Each <code>int[]</code> contains a complete set of parameters to the setter method. @@ -249,6 +297,26 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates the target using a multi-int setter + * along the given <code>Path</code>. A <code>Path</code></> animation moves in two dimensions, + * animating coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are integer x and y coordinates used in the first and second parameter of the + * setter, respectively. + * + * @param target The object whose property is to be animated. This object may + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also + * be the case-sensitive complete name of the public setter method. + * @param propertyName The name of the property being animated or the name of the setter method. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static ObjectAnimator ofMultiInt(Object target, String propertyName, Path path) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, path); + return ofPropertyValuesHolder(target, pvh); + } + + /** * Constructs and returns an ObjectAnimator that animates over values for a multiple int * parameters setter. Only public methods that take only int parameters are supported. * <p>At least two values must be provided, a start and end. More than two @@ -258,7 +326,7 @@ public final class ObjectAnimator extends ValueAnimator { * @param target The object whose property is to be animated. This object may * have a public method on it called <code>setName()</code>, where <code>name</code> is * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also - * be the complete name of the public method. + * be the case-sensitive complete name of the public setter method. * @param propertyName The name of the property being animated or the name of the setter method. * @param converter Converts T objects into int parameters for the multi-value setter. * @param evaluator A TypeEvaluator that will be called on each animation frame to @@ -334,6 +402,31 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code> + * using two properties. A <code>Path</code></> animation moves in two dimensions, animating + * coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are floats that are set to separate properties designated by + * <code>xPropertyName</code> and <code>yPropertyName</code>. + * + * @param target The object whose properties are to be animated. This object should + * have public methods on it called <code>setNameX()</code> and + * <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code> + * are the value of the <code>xPropertyName</code> and <code>yPropertyName</code> + * parameters, respectively. + * @param xPropertyName The name of the property for the x coordinate being animated. + * @param yPropertyName The name of the property for the y coordinate being animated. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + 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]); + return ofPropertyValuesHolder(target, x, y); + } + + /** * Constructs and returns an ObjectAnimator that animates between float values. A single * value implies that that value is the one being animated to. Two values imply starting * and ending values. More than two values imply a starting value, values to animate through @@ -353,6 +446,24 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code> + * using two properties. A <code>Path</code></> animation moves in two dimensions, animating + * coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are floats that are set to separate properties, <code>xProperty</code> and + * <code>yProperty</code>. + * + * @param target The object whose properties are to be animated. + * @param xProperty The property for the x coordinate being animated. + * @param yProperty The property for the y coordinate being animated. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> xProperty, + Property<T, Float> yProperty, Path path) { + return ofFloat(target, xProperty.getName(), yProperty.getName(), path); + } + + /** * Constructs and returns an ObjectAnimator that animates over float values for a multiple * parameters setter. Only public methods that take only float parameters are supported. * Each <code>float[]</code> contains a complete set of parameters to the setter method. @@ -363,7 +474,7 @@ public final class ObjectAnimator extends ValueAnimator { * @param target The object whose property is to be animated. This object may * have a public method on it called <code>setName()</code>, where <code>name</code> is * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also - * be the complete name of the public method. + * be the case-sensitive complete name of the public setter method. * @param propertyName The name of the property being animated or the name of the setter method. * @param values A set of values that the animation will animate between over time. * @return An ObjectAnimator object that is set up to animate between the given values. @@ -375,6 +486,26 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates the target using a multi-float setter + * along the given <code>Path</code>. A <code>Path</code></> animation moves in two dimensions, + * animating coordinates <code>(x, y)</code> together to follow the line. In this variation, the + * coordinates are float x and y coordinates used in the first and second parameter of the + * setter, respectively. + * + * @param target The object whose property is to be animated. This object may + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also + * be the case-sensitive complete name of the public setter method. + * @param propertyName The name of the property being animated or the name of the setter method. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static ObjectAnimator ofMultiFloat(Object target, String propertyName, Path path) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, path); + return ofPropertyValuesHolder(target, pvh); + } + + /** * Constructs and returns an ObjectAnimator that animates over values for a multiple float * parameters setter. Only public methods that take only float parameters are supported. * <p>At least two values must be provided, a start and end. More than two @@ -426,6 +557,30 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates a property along a <code>Path</code>. + * A <code>Path</code></> animation moves in two dimensions, animating coordinates + * <code>(x, y)</code> together to follow the line. This variant animates the coordinates + * in a <code>PointF</code> to follow the <code>Path</code>. If the <code>Property</code> + * associated with <code>propertyName</code> 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>. + * + * @param target The object whose property is to be animated. This object should + * have a public method on it called <code>setName()</code>, where <code>name</code> is + * the value of the <code>propertyName</code> parameter. + * @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. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static ObjectAnimator ofObject(Object target, String propertyName, + TypeConverter<PointF, ?> converter, Path path) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(propertyName, converter, path); + return ofPropertyValuesHolder(target, pvh); + } + + /** * Constructs and returns an ObjectAnimator that animates between Object values. A single * value implies that that value is the one being animated to. Two values imply starting * and ending values. More than two values imply a starting value, values to animate through @@ -475,6 +630,27 @@ public final class ObjectAnimator extends ValueAnimator { } /** + * Constructs and returns an ObjectAnimator that animates a property along a <code>Path</code>. + * A <code>Path</code></> animation moves in two dimensions, animating coordinates + * <code>(x, y)</code> together to follow the line. This variant animates the coordinates + * in a <code>PointF</code> to follow the <code>Path</code>. If <code>property</code> + * 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>. + * + * @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 + * null if conversion is unnecessary. + * @param path The <code>Path</code> to animate values along. + * @return An ObjectAnimator object that is set up to animate along <code>path</code>. + */ + public static <T, V> ObjectAnimator ofObject(T target, Property<T, V> property, + TypeConverter<PointF, V> converter, Path path) { + PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(property, converter, path); + return ofPropertyValuesHolder(target, pvh); + } + + /** * Constructs and returns an ObjectAnimator that animates between the sets of values specified * in <code>PropertyValueHolder</code> objects. This variant should be used when animating * several properties at once with the same ObjectAnimator, since PropertyValuesHolder allows diff --git a/core/java/android/animation/PointFEvaluator.java b/core/java/android/animation/PointFEvaluator.java new file mode 100644 index 0000000..91d501f --- /dev/null +++ b/core/java/android/animation/PointFEvaluator.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2013 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.PointF; + +/** + * This evaluator can be used to perform type interpolation between <code>PointF</code> values. + */ +public class PointFEvaluator implements TypeEvaluator<PointF> { + + /** + * When null, a new PointF is returned on every evaluate call. When non-null, + * mPoint will be modified and returned on every evaluate. + */ + private PointF mPoint; + + /** + * Construct a PointFEvaluator that returns a new PointF on every evaluate call. + * To avoid creating an object for each evaluate call, + * {@link PointFEvaluator#PointFEvaluator(android.graphics.PointF)} should be used + * whenever possible. + */ + public PointFEvaluator() { + } + + /** + * Constructs a PointFEvaluator that modifies and returns <code>reuse</code> + * in {@link #evaluate(float, android.graphics.PointF, android.graphics.PointF)} calls. + * The value returned from + * {@link #evaluate(float, android.graphics.PointF, android.graphics.PointF)} should + * not be cached because it will change over time as the object is reused on each + * call. + * + * @param reuse A PointF to be modified and returned by evaluate. + */ + public PointFEvaluator(PointF reuse) { + mPoint = reuse; + } + + /** + * This function returns the result of linearly interpolating the start and + * end PointF values, with <code>fraction</code> representing the proportion + * between the start and end values. The calculation is a simple parametric + * calculation on each of the separate components in the PointF objects + * (x, y). + * + * <p>If {@link #PointFEvaluator(android.graphics.PointF)} was used to construct + * this PointFEvaluator, the object returned will be the <code>reuse</code> + * passed into the constructor.</p> + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start PointF + * @param endValue The end PointF + * @return A linear interpolation between the start and end values, given the + * <code>fraction</code> parameter. + */ + @Override + public PointF evaluate(float fraction, PointF startValue, PointF endValue) { + float x = startValue.x + (fraction * (endValue.x - startValue.x)); + float y = startValue.y + (fraction * (endValue.y - startValue.y)); + + if (mPoint != null) { + mPoint.set(x, y); + return mPoint; + } else { + return new PointF(x, y); + } + } +} diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java index c291970..1b028e0 100644 --- a/core/java/android/animation/PropertyValuesHolder.java +++ b/core/java/android/animation/PropertyValuesHolder.java @@ -16,6 +16,9 @@ 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; @@ -176,8 +179,8 @@ public class PropertyValuesHolder implements Cloneable { * start, through all intermediate values to the end value. When used with ObjectAnimator, * the elements of the array represent the parameters of the setter function. * - * @param propertyName The name of the property being animated. Can also be the name of the - * entire setter method. Should not be null. + * @param propertyName The name of the property being animated. Can also be the + * case-sensitive name of the entire setter method. Should not be null. * @param values The values that the property will animate between. * @return PropertyValuesHolder The constructed PropertyValuesHolder object. * @see IntArrayEvaluator#IntArrayEvaluator(int[]) @@ -204,6 +207,26 @@ public class PropertyValuesHolder implements Cloneable { } /** + * Constructs and returns a PropertyValuesHolder with a given property name to use + * as a multi-int setter. The values are animated along the path, with the first + * parameter of the setter set to the x coordinate and the second set to the y coordinate. + * + * @param propertyName The name of the property being animated. Can also be the + * case-sensitive name of the entire setter method. Should not be null. + * The setter must take exactly two <code>int</code> parameters. + * @param path The Path along which the values should be animated. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + * @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()); + PointFToIntArray converter = new PointFToIntArray(); + return new MultiIntValuesHolder(propertyName, converter, evaluator, keyframeSet); + } + + /** * Constructs and returns a PropertyValuesHolder with a given property and * set of Object values for use with ObjectAnimator multi-value setters. The Object * values are converted to <code>int[]</code> using the converter. @@ -276,8 +299,8 @@ public class PropertyValuesHolder implements Cloneable { * start, through all intermediate values to the end value. When used with ObjectAnimator, * the elements of the array represent the parameters of the setter function. * - * @param propertyName The name of the property being animated. Can also be the name of the - * entire setter method. Should not be null. + * @param propertyName The name of the property being animated. Can also be the + * case-sensitive name of the entire setter method. Should not be null. * @param values The values that the property will animate between. * @return PropertyValuesHolder The constructed PropertyValuesHolder object. * @see FloatArrayEvaluator#FloatArrayEvaluator(float[]) @@ -304,6 +327,26 @@ public class PropertyValuesHolder implements Cloneable { } /** + * Constructs and returns a PropertyValuesHolder with a given property name to use + * as a multi-float setter. The values are animated along the path, with the first + * parameter of the setter set to the x coordinate and the second set to the y coordinate. + * + * @param propertyName The name of the property being animated. Can also be the + * case-sensitive name of the entire setter method. Should not be null. + * The setter must take exactly two <code>float</code> parameters. + * @param path The Path along which the values should be animated. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + * @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()); + PointFToFloatArray converter = new PointFToFloatArray(); + return new MultiFloatValuesHolder(propertyName, converter, evaluator, keyframeSet); + } + + /** * Constructs and returns a PropertyValuesHolder with a given property and * set of Object values for use with ObjectAnimator multi-value setters. The Object * values are converted to <code>float[]</code> using the converter. @@ -367,6 +410,27 @@ public class PropertyValuesHolder implements Cloneable { } /** + * Constructs and returns a PropertyValuesHolder with a given property name and + * a Path along which the values should be animated. This variant supports a + * <code>TypeConverter</code> to convert from <code>PointF</code> to the target + * type. + * + * @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. + * @param path The Path along which the values should be animated. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + 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())); + pvh.setConverter(converter); + return pvh; + } + + /** * Constructs and returns a PropertyValuesHolder with a given property and * set of Object values. This variant also takes a TypeEvaluator because the system * cannot automatically interpolate between objects of unknown type. @@ -415,6 +479,27 @@ public class PropertyValuesHolder implements Cloneable { } /** + * Constructs and returns a PropertyValuesHolder with a given property and + * a Path along which the values should be animated. This variant supports a + * <code>TypeConverter</code> to convert from <code>PointF</code> to the target + * type. + * + * @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. + * @param path The Path along which the values should be animated. + * @return PropertyValuesHolder The constructed PropertyValuesHolder object. + */ + 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())); + pvh.setConverter(converter); + return pvh; + } + + /** * Constructs and returns a PropertyValuesHolder object with the specified property name and set * of values. These values can be of any type, but the type should be consistent so that * an appropriate {@link android.animation.TypeEvaluator} can be found that matches @@ -1439,6 +1524,113 @@ 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. + */ + private static class PointFToFloatArray extends TypeConverter<PointF, float[]> { + private float[] mCoordinates = new float[2]; + + public PointFToFloatArray() { + super(PointF.class, float[].class); + } + + @Override + public float[] convert(PointF value) { + mCoordinates[0] = value.x; + mCoordinates[1] = value.y; + return mCoordinates; + } + }; + + /** + * Convert from PointF to int[] for multi-int setters along a Path. + */ + private static class PointFToIntArray extends TypeConverter<PointF, int[]> { + private int[] mCoordinates = new int[2]; + + public PointFToIntArray() { + super(PointF.class, int[].class); + } + + @Override + public int[] convert(PointF value) { + mCoordinates[0] = Math.round(value.x); + mCoordinates[1] = Math.round(value.y); + return mCoordinates; + } + }; + native static private int nGetIntMethod(Class targetClass, String methodName); native static private int nGetFloatMethod(Class targetClass, String methodName); native static private int nGetMultipleIntMethod(Class targetClass, String methodName, @@ -1456,4 +1648,4 @@ public class PropertyValuesHolder implements Cloneable { native static private void nCallFourFloatMethod(Object target, int methodID, float arg1, float arg2, float arg3, float arg4); native static private void nCallMultipleFloatMethod(Object target, int methodID, float[] args); -}
\ No newline at end of file +} diff --git a/core/jni/android/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp index ab7f1dc..97a7de6 100644 --- a/core/jni/android/graphics/Path.cpp +++ b/core/jni/android/graphics/Path.cpp @@ -28,6 +28,8 @@ #include "pathops/SkPathOps.h" #include <Caches.h> +#include <vector> +#include <map> namespace android { @@ -265,6 +267,196 @@ public: static jboolean op(JNIEnv* env, jobject clazz, SkPath* p1, SkPath* p2, SkPathOp op, SkPath* r) { return Op(*p1, *p2, op, r); } + + + typedef SkPoint (*bezierCalculation)(float t, const SkPoint* points); + + static void addMove(std::vector<SkPoint>& segmentPoints, std::vector<float>& lengths, + const SkPoint& point) { + float length = 0; + if (!lengths.empty()) { + length = lengths.back(); + } + segmentPoints.push_back(point); + lengths.push_back(length); + } + + static void addLine(std::vector<SkPoint>& segmentPoints, std::vector<float>& lengths, + const SkPoint& toPoint) { + if (segmentPoints.empty()) { + segmentPoints.push_back(SkPoint::Make(0, 0)); + lengths.push_back(0); + } else if (segmentPoints.back() == toPoint) { + return; // Empty line + } + float length = lengths.back() + SkPoint::Distance(segmentPoints.back(), toPoint); + segmentPoints.push_back(toPoint); + lengths.push_back(length); + } + + static float cubicCoordinateCalculation(float t, float p0, float p1, float p2, float p3) { + float oneMinusT = 1 - t; + float oneMinusTSquared = oneMinusT * oneMinusT; + float oneMinusTCubed = oneMinusTSquared * oneMinusT; + float tSquared = t * t; + float tCubed = tSquared * t; + return (oneMinusTCubed * p0) + (3 * oneMinusTSquared * t * p1) + + (3 * oneMinusT * tSquared * p2) + (tCubed * p3); + } + + static SkPoint cubicBezierCalculation(float t, const SkPoint* points) { + float x = cubicCoordinateCalculation(t, points[0].x(), points[1].x(), + points[2].x(), points[3].x()); + float y = cubicCoordinateCalculation(t, points[0].y(), points[1].y(), + points[2].y(), points[3].y()); + return SkPoint::Make(x, y); + } + + static float quadraticCoordinateCalculation(float t, float p0, float p1, float p2) { + float oneMinusT = 1 - t; + return oneMinusT * ((oneMinusT * p0) + (t * p1)) + t * ((oneMinusT * p1) + (t * p2)); + } + + static SkPoint quadraticBezierCalculation(float t, const SkPoint* points) { + float x = quadraticCoordinateCalculation(t, points[0].x(), points[1].x(), points[2].x()); + float y = quadraticCoordinateCalculation(t, points[0].y(), points[1].y(), points[2].y()); + return SkPoint::Make(x, y); + } + + // Subdivide a section of the Bezier curve, set the mid-point and the mid-t value. + // Returns true if further subdivision is necessary as defined by errorSquared. + static bool subdividePoints(const SkPoint* points, bezierCalculation bezierFunction, + float t0, const SkPoint &p0, float t1, const SkPoint &p1, + float& midT, SkPoint &midPoint, float errorSquared) { + midT = (t1 + t0) / 2; + float midX = (p1.x() + p0.x()) / 2; + float midY = (p1.y() + p0.y()) / 2; + + midPoint = (*bezierFunction)(midT, points); + float xError = midPoint.x() - midX; + float yError = midPoint.y() - midY; + float midErrorSquared = (xError * xError) + (yError * yError); + return midErrorSquared > errorSquared; + } + + // Divides Bezier curves until linear interpolation is very close to accurate, using + // errorSquared as a metric. Cubic Bezier curves can have an inflection point that improperly + // short-circuit subdivision. If you imagine an S shape, the top and bottom points being the + // starting and end points, linear interpolation would mark the center where the curve places + // the point. It is clearly not the case that we can linearly interpolate at that point. + // doubleCheckDivision forces a second examination between subdivisions to ensure that linear + // interpolation works. + static void addBezier(const SkPoint* points, + bezierCalculation bezierFunction, std::vector<SkPoint>& segmentPoints, + std::vector<float>& lengths, float errorSquared, bool doubleCheckDivision) { + typedef std::map<float, SkPoint> PointMap; + PointMap tToPoint; + + tToPoint[0] = (*bezierFunction)(0, points); + tToPoint[1] = (*bezierFunction)(1, points); + + PointMap::iterator iter = tToPoint.begin(); + PointMap::iterator next = iter; + ++next; + while (next != tToPoint.end()) { + bool needsSubdivision = true; + SkPoint midPoint; + do { + float midT; + needsSubdivision = subdividePoints(points, bezierFunction, iter->first, + iter->second, next->first, next->second, midT, midPoint, errorSquared); + if (!needsSubdivision && doubleCheckDivision) { + SkPoint quarterPoint; + float quarterT; + needsSubdivision = subdividePoints(points, bezierFunction, iter->first, + iter->second, midT, midPoint, quarterT, quarterPoint, errorSquared); + if (needsSubdivision) { + // Found an inflection point. No need to double-check. + doubleCheckDivision = false; + } + } + if (needsSubdivision) { + next = tToPoint.insert(iter, PointMap::value_type(midT, midPoint)); + } + } while (needsSubdivision); + iter = next; + next++; + } + + // Now that each division can use linear interpolation with less than the allowed error + for (iter = tToPoint.begin(); iter != tToPoint.end(); ++iter) { + addLine(segmentPoints, lengths, iter->second); + } + } + + static void createVerbSegments(SkPath::Verb verb, const SkPoint* points, + std::vector<SkPoint>& segmentPoints, std::vector<float>& lengths, float errorSquared) { + switch (verb) { + case SkPath::kMove_Verb: + addMove(segmentPoints, lengths, points[0]); + break; + case SkPath::kClose_Verb: + case SkPath::kLine_Verb: + addLine(segmentPoints, lengths, points[1]); + break; + case SkPath::kQuad_Verb: + addBezier(points, quadraticBezierCalculation, segmentPoints, lengths, + errorSquared, false); + break; + case SkPath::kCubic_Verb: + addBezier(points, cubicBezierCalculation, segmentPoints, lengths, + errorSquared, true); + break; + default: + // Leave element as NULL, Conic sections are not supported. + break; + } + } + + // Returns a float[] with each point along the path represented by 3 floats + // * fractional length along the path that the point resides + // * x coordinate + // * y coordinate + // Note that more than one point may have the same length along the path in + // the case of a move. + // NULL can be returned if the Path is empty. + static jfloatArray approximate(JNIEnv* env, jclass, SkPath* path, float acceptableError) + { + SkASSERT(path); + SkPath::Iter pathIter(*path, false); + SkPath::Verb verb; + SkPoint points[4]; + std::vector<SkPoint> segmentPoints; + std::vector<float> lengths; + float errorSquared = acceptableError * acceptableError; + + while ((verb = pathIter.next(points)) != SkPath::kDone_Verb) { + createVerbSegments(verb, points, segmentPoints, lengths, errorSquared); + } + + if (segmentPoints.empty()) { + return NULL; + } + + size_t numPoints = segmentPoints.size(); + size_t approximationArraySize = numPoints * 3; + + float* approximation = new float[approximationArraySize]; + float totalLength = lengths.back(); + + int approximationIndex = 0; + for (int i = 0; i < numPoints; i++) { + const SkPoint& point = segmentPoints[i]; + approximation[approximationIndex++] = lengths[i] / totalLength; + approximation[approximationIndex++] = point.x(); + approximation[approximationIndex++] = point.y(); + } + + jfloatArray result = env->NewFloatArray(approximationArraySize); + env->SetFloatArrayRegion(result, 0, approximationArraySize, approximation); + delete[] approximation; + return result; + } }; static JNINativeMethod methods[] = { @@ -305,7 +497,8 @@ static JNINativeMethod methods[] = { {"native_setLastPoint","(IFF)V", (void*) SkPathGlue::setLastPoint}, {"native_transform","(III)V", (void*) SkPathGlue::transform__MatrixPath}, {"native_transform","(II)V", (void*) SkPathGlue::transform__Matrix}, - {"native_op","(IIII)Z", (void*) SkPathGlue::op} + {"native_op","(IIII)Z", (void*) SkPathGlue::op}, + {"native_approximate", "(IF)[F", (void*) SkPathGlue::approximate}, }; int register_android_graphics_Path(JNIEnv* env) { diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index 09481d4..1a7e3ec 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -692,6 +692,28 @@ public class Path { return mNativePath; } + /** + * Approximate the <code>Path</code> with a series of line segments. + * This returns float[] with the array containing point components. + * There are three components for each point, in order: + * <ul> + * <li>Fraction along the length of the path that the point resides</li> + * <li>The x coordinate of the point</li> + * <li>The y coordinate of the point</li> + * </ul> + * <p>Two points may share the same fraction along its length when there is + * a move action within the Path.</p> + * + * @param acceptableError The acceptable error for a line on the + * Path. Typically this would be 0.5 so that + * the error is less than half a pixel. + * @return An array of components for points approximating the Path. + * @hide + */ + public float[] approximate(float acceptableError) { + return native_approximate(mNativePath, acceptableError); + } + private static native int init1(); private static native int init2(int nPath); private static native void native_reset(int nPath); @@ -738,4 +760,5 @@ public class Path { private static native void native_transform(int nPath, int matrix); private static native boolean native_op(int path1, int path2, int op, int result); private static native void finalizer(int nPath); + private static native float[] native_approximate(int nPath, float error); } |