Add animations along a Path.

Change-Id: If03bd10a3961ff874e33489e1253146b8dadca33
This commit is contained in:
George Mount
2013-08-23 13:31:31 -07:00
parent 17a93ff130
commit c96c7b2e54
6 changed files with 693 additions and 8 deletions

View File

@@ -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...);

View File

@@ -16,6 +16,8 @@
package android.animation;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.Log;
import android.util.Property;
@@ -209,6 +211,31 @@ public final class ObjectAnimator extends ValueAnimator {
return anim;
}
/**
* 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
@@ -227,6 +254,27 @@ public final class ObjectAnimator extends ValueAnimator {
return anim;
}
/**
* 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.
@@ -248,6 +296,26 @@ public final class ObjectAnimator extends ValueAnimator {
return ofPropertyValuesHolder(target, pvh);
}
/**
* 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.
@@ -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
@@ -333,6 +401,31 @@ public final class ObjectAnimator extends ValueAnimator {
return anim;
}
/**
* 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
@@ -352,6 +445,24 @@ public final class ObjectAnimator extends ValueAnimator {
return anim;
}
/**
* 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.
@@ -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.
@@ -374,6 +485,26 @@ public final class ObjectAnimator extends ValueAnimator {
return ofPropertyValuesHolder(target, pvh);
}
/**
* 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.
@@ -425,6 +556,30 @@ public final class ObjectAnimator extends ValueAnimator {
return anim;
}
/**
* 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
@@ -474,6 +629,27 @@ public final class ObjectAnimator extends ValueAnimator {
return ofPropertyValuesHolder(target, pvh);
}
/**
* 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

View File

@@ -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);
}
}
}

View File

@@ -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[])
@@ -203,6 +206,26 @@ public class PropertyValuesHolder implements Cloneable {
return new MultiIntValuesHolder(propertyName, null, evaluator, (Object[]) values);
}
/**
* 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
@@ -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[])
@@ -303,6 +326,26 @@ public class PropertyValuesHolder implements Cloneable {
return new MultiFloatValuesHolder(propertyName, null, evaluator, (Object[]) values);
}
/**
* 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
@@ -366,6 +409,27 @@ public class PropertyValuesHolder implements Cloneable {
return pvh;
}
/**
* 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
@@ -414,6 +478,27 @@ public class PropertyValuesHolder implements Cloneable {
return pvh;
}
/**
* 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
@@ -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);
}
}

View File

@@ -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) {

View File

@@ -703,6 +703,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);
@@ -749,4 +771,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);
}