summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/java/android/animation/AnimatorInflater.java266
-rw-r--r--core/java/android/util/PathParser.java83
-rw-r--r--core/res/res/values/attrs.xml3
3 files changed, 282 insertions, 70 deletions
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 39fcf73..0f5e954 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -23,10 +23,12 @@ import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Path;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.PathParser;
import android.util.StateSet;
import android.util.TypedValue;
import android.util.Xml;
+import android.view.InflateException;
import android.view.animation.AnimationUtils;
import com.android.internal.R;
@@ -47,7 +49,7 @@ import java.util.ArrayList;
* <em>something</em> file.)
*/
public class AnimatorInflater {
-
+ private static final String TAG = "AnimatorInflater";
/**
* These flags are used when parsing AnimatorSet objects
*/
@@ -59,9 +61,12 @@ public class AnimatorInflater {
*/
private static final int VALUE_TYPE_FLOAT = 0;
private static final int VALUE_TYPE_INT = 1;
+ private static final int VALUE_TYPE_PATH = 2;
private static final int VALUE_TYPE_COLOR = 4;
private static final int VALUE_TYPE_CUSTOM = 5;
+ private static final boolean DBG_ANIMATOR_INFLATER = false;
+
/**
* Loads an {@link Animator} object from a resource
*
@@ -189,6 +194,56 @@ public class AnimatorInflater {
}
/**
+ * PathDataEvaluator is used to interpolate between two paths which are
+ * represented in the same format but different control points' values.
+ * The path is represented as an array of PathDataNode here, which is
+ * fundamentally an array of floating point numbers.
+ */
+ private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathDataNode[]> {
+ private PathParser.PathDataNode[] mNodeArray;
+
+ /**
+ * Create a PathParser.PathDataNode[] that does not reuse the animated value.
+ * Care must be taken when using this option because on every evaluation
+ * a new <code>PathParser.PathDataNode[]</code> will be allocated.
+ */
+ private PathDataEvaluator() {}
+
+ /**
+ * Create a PathDataEvaluator that reuses <code>nodeArray</code> for every evaluate() call.
+ * Caution must be taken to ensure that the value returned from
+ * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
+ * used across threads. The value will be modified on each <code>evaluate()</code> call.
+ *
+ * @param nodeArray The array to modify and return from <code>evaluate</code>.
+ */
+ public PathDataEvaluator(PathParser.PathDataNode[] nodeArray) {
+ mNodeArray = nodeArray;
+ }
+
+ @Override
+ public PathParser.PathDataNode[] evaluate(float fraction,
+ PathParser.PathDataNode[] startPathData,
+ PathParser.PathDataNode[] endPathData) {
+ if (!PathParser.canMorph(startPathData, endPathData)) {
+ throw new IllegalArgumentException("Can't interpolate between"
+ + " two incompatible pathData");
+ }
+
+ if (mNodeArray == null || !PathParser.canMorph(mNodeArray, startPathData)) {
+ mNodeArray = PathParser.deepCopyNodes(startPathData);
+ }
+
+ for (int i = 0; i < startPathData.length; i++) {
+ mNodeArray[i].interpolatePathDataNode(startPathData[i],
+ endPathData[i], fraction);
+ }
+
+ return mNodeArray;
+ }
+ }
+
+ /**
* @param anim Null if this is a ValueAnimator, otherwise this is an
* ObjectAnimator
* @param arrayAnimator Incoming typed array for Animator's attributes.
@@ -209,27 +264,157 @@ public class AnimatorInflater {
}
TypeEvaluator evaluator = null;
- int valueFromIndex = R.styleable.Animator_valueFrom;
- int valueToIndex = R.styleable.Animator_valueTo;
boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
- TypedValue tvFrom = arrayAnimator.peekValue(valueFromIndex);
+ TypedValue tvFrom = arrayAnimator.peekValue(R.styleable.Animator_valueFrom);
boolean hasFrom = (tvFrom != null);
int fromType = hasFrom ? tvFrom.type : 0;
- TypedValue tvTo = arrayAnimator.peekValue(valueToIndex);
+ TypedValue tvTo = arrayAnimator.peekValue(R.styleable.Animator_valueTo);
boolean hasTo = (tvTo != null);
int toType = hasTo ? tvTo.type : 0;
- if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
- (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
- // special case for colors: ignore valueType and get ints
- getFloats = false;
- evaluator = ArgbEvaluator.getInstance();
+ // TODO: Further clean up this part of code into 4 types : path, color,
+ // integer and float.
+ if (valueType == VALUE_TYPE_PATH) {
+ evaluator = setupAnimatorForPath(anim, arrayAnimator);
+ } else {
+ // Integer and float value types are handled here.
+ if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+ (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
+ (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+ (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
+ // special case for colors: ignore valueType and get ints
+ getFloats = false;
+ evaluator = ArgbEvaluator.getInstance();
+ }
+ setupValues(anim, arrayAnimator, getFloats, hasFrom, fromType, hasTo, toType);
}
+ anim.setDuration(duration);
+ anim.setStartDelay(startDelay);
+
+ if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) {
+ anim.setRepeatCount(
+ arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0));
+ }
+ if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) {
+ anim.setRepeatMode(
+ arrayAnimator.getInt(R.styleable.Animator_repeatMode,
+ ValueAnimator.RESTART));
+ }
+ if (evaluator != null) {
+ anim.setEvaluator(evaluator);
+ }
+
+ if (arrayObjectAnimator != null) {
+ setupObjectAnimator(anim, arrayObjectAnimator, getFloats);
+ }
+ }
+
+ /**
+ * Setup the Animator to achieve path morphing.
+ *
+ * @param anim The target Animator which will be updated.
+ * @param arrayAnimator TypedArray for the ValueAnimator.
+ * @return the PathDataEvaluator.
+ */
+ private static TypeEvaluator setupAnimatorForPath(ValueAnimator anim,
+ TypedArray arrayAnimator) {
+ TypeEvaluator evaluator = null;
+ String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom);
+ String toString = arrayAnimator.getString(R.styleable.Animator_valueTo);
+ PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString);
+ PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString);
+
+ if (nodesFrom != null) {
+ if (nodesTo != null) {
+ anim.setObjectValues(nodesFrom, nodesTo);
+ if (!PathParser.canMorph(nodesFrom, nodesTo)) {
+ throw new InflateException(arrayAnimator.getPositionDescription()
+ + " Can't morph from " + fromString + " to " + toString);
+ }
+ } else {
+ anim.setObjectValues((Object)nodesFrom);
+ }
+ evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom));
+ } else if (nodesTo != null) {
+ anim.setObjectValues((Object)nodesTo);
+ evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo));
+ }
+
+ if (DBG_ANIMATOR_INFLATER && evaluator != null) {
+ Log.v(TAG, "create a new PathDataEvaluator here");
+ }
+
+ return evaluator;
+ }
+
+ /**
+ * Setup ObjectAnimator's property or values from pathData.
+ *
+ * @param anim The target Animator which will be updated.
+ * @param arrayObjectAnimator TypedArray for the ObjectAnimator.
+ * @param getFloats True if the value type is float.
+ */
+ private static void setupObjectAnimator(ValueAnimator anim, TypedArray arrayObjectAnimator,
+ boolean getFloats) {
+ ObjectAnimator oa = (ObjectAnimator) anim;
+ String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData);
+
+ // Note that if there is a pathData defined in the Object Animator,
+ // valueFrom / valueTo will be ignored.
+ if (pathData != null) {
+ String propertyXName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName);
+ String propertyYName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyYName);
+
+ if (propertyXName == null && propertyYName == null) {
+ throw new InflateException(arrayObjectAnimator.getPositionDescription()
+ + " propertyXName or propertyYName is needed for PathData");
+ } else {
+ Path path = PathParser.createPathFromPathData(pathData);
+ Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, !getFloats);
+ PropertyValuesHolder x = null;
+ PropertyValuesHolder y = null;
+ if (propertyXName != null) {
+ x = PropertyValuesHolder.ofKeyframe(propertyXName, keyframes[0]);
+ }
+ if (propertyYName != null) {
+ y = PropertyValuesHolder.ofKeyframe(propertyYName, keyframes[1]);
+ }
+ if (x == null) {
+ oa.setValues(y);
+ } else if (y == null) {
+ oa.setValues(x);
+ } else {
+ oa.setValues(x, y);
+ }
+ }
+ } else {
+ String propertyName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyName);
+ oa.setPropertyName(propertyName);
+ }
+ }
+
+ /**
+ * Setup ValueAnimator's values.
+ * This will handle all of the integer, float and color types.
+ *
+ * @param anim The target Animator which will be updated.
+ * @param arrayAnimator TypedArray for the ValueAnimator.
+ * @param getFloats True if the value type is float.
+ * @param hasFrom True if "valueFrom" exists.
+ * @param fromType The type of "valueFrom".
+ * @param hasTo True if "valueTo" exists.
+ * @param toType The type of "valueTo".
+ */
+ private static void setupValues(ValueAnimator anim, TypedArray arrayAnimator,
+ boolean getFloats, boolean hasFrom, int fromType, boolean hasTo, int toType) {
+ int valueFromIndex = R.styleable.Animator_valueFrom;
+ int valueToIndex = R.styleable.Animator_valueTo;
if (getFloats) {
float valueFrom;
float valueTo;
@@ -296,63 +481,6 @@ public class AnimatorInflater {
}
}
}
-
- anim.setDuration(duration);
- anim.setStartDelay(startDelay);
-
- if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) {
- anim.setRepeatCount(
- arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0));
- }
- if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) {
- anim.setRepeatMode(
- arrayAnimator.getInt(R.styleable.Animator_repeatMode,
- ValueAnimator.RESTART));
- }
- if (evaluator != null) {
- anim.setEvaluator(evaluator);
- }
-
- if (arrayObjectAnimator != null) {
- ObjectAnimator oa = (ObjectAnimator) anim;
- String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData);
-
- // Note that if there is a pathData defined in the Object Animator,
- // valueFrom / valueTo will be overwritten by the pathData.
- if (pathData != null) {
- String propertyXName =
- arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName);
- String propertyYName =
- arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyYName);
-
- if (propertyXName == null && propertyYName == null) {
- throw new IllegalArgumentException("propertyXName or propertyYName"
- + " is needed for PathData in Object Animator");
- } else {
- Path path = PathParser.createPathFromPathData(pathData);
- Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, !getFloats);
- PropertyValuesHolder x = null;
- PropertyValuesHolder y = null;
- if (propertyXName != null) {
- x = PropertyValuesHolder.ofKeyframe(propertyXName, keyframes[0]);
- }
- if (propertyYName != null) {
- y = PropertyValuesHolder.ofKeyframe(propertyYName, keyframes[1]);
- }
- if (x == null) {
- oa.setValues(y);
- } else if (y == null) {
- oa.setValues(x);
- } else {
- oa.setValues(x, y);
- }
- }
- } else {
- String propertyName =
- arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyName);
- oa.setPropertyName(propertyName);
- }
- }
}
private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser)
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index f90ce51..f4a0448 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -45,6 +45,9 @@ public class PathParser {
* @return an array of the PathDataNode.
*/
public static PathDataNode[] createNodesFromPathData(String pathData) {
+ if (pathData == null) {
+ return null;
+ }
int start = 0;
int end = 1;
@@ -64,6 +67,57 @@ public class PathParser {
return list.toArray(new PathDataNode[list.size()]);
}
+ /**
+ * @param source The array of PathDataNode to be duplicated.
+ * @return a deep copy of the <code>source</code>.
+ */
+ public static PathDataNode[] deepCopyNodes(PathDataNode[] source) {
+ PathDataNode[] copy = new PathParser.PathDataNode[source.length];
+ for (int i = 0; i < source.length; i ++) {
+ copy[i] = new PathDataNode(source[i]);
+ }
+ return copy;
+ }
+
+ /**
+ * @param nodesFrom The source path represented in an array of PathDataNode
+ * @param nodesTo The target path represented in an array of PathDataNode
+ * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
+ */
+ public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
+ if (nodesFrom == null || nodesTo == null) {
+ return false;
+ }
+
+ if (nodesFrom.length != nodesTo.length) {
+ return false;
+ }
+
+ for (int i = 0; i < nodesFrom.length; i ++) {
+ if (nodesFrom[i].mType != nodesTo[i].mType
+ || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Update the target's data to match the source.
+ * Before calling this, make sure canMorph(target, source) is true.
+ *
+ * @param target The target path represented in an array of PathDataNode
+ * @param source The source path represented in an array of PathDataNode
+ */
+ public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
+ for (int i = 0; i < source.length; i ++) {
+ target[i].mType = source[i].mType;
+ for (int j = 0; j < source[i].mParams.length; j ++) {
+ target[i].mParams[j] = source[i].mParams[j];
+ }
+ }
+ }
+
private static int nextStart(String s, int end) {
char c;
@@ -132,6 +186,11 @@ public class PathParser {
return (comma > space) ? space : comma;
}
+ /**
+ * Each PathDataNode represents one command in the "d" attribute of the svg
+ * file.
+ * An array of PathDataNode can represent the whole "d" attribute.
+ */
public static class PathDataNode {
private char mType;
private float[] mParams;
@@ -146,6 +205,12 @@ public class PathParser {
mParams = Arrays.copyOf(n.mParams, n.mParams.length);
}
+ /**
+ * Convert an array of PathDataNode to Path.
+ *
+ * @param node The source array of PathDataNode.
+ * @param path The target Path object.
+ */
public static void nodesToPath(PathDataNode[] node, Path path) {
float[] current = new float[4];
char previousCommand = 'm';
@@ -155,6 +220,23 @@ public class PathParser {
}
}
+ /**
+ * The current PathDataNode will be interpolated between the
+ * <code>nodeFrom</code> and <code>nodeTo</code> according to the
+ * <code>fraction</code>.
+ *
+ * @param nodeFrom The start value as a PathDataNode.
+ * @param nodeTo The end value as a PathDataNode
+ * @param fraction The fraction to interpolate.
+ */
+ public void interpolatePathDataNode(PathDataNode nodeFrom,
+ PathDataNode nodeTo, float fraction) {
+ for (int i = 0; i < nodeFrom.mParams.length; i++) {
+ mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
+ + nodeTo.mParams[i] * fraction;
+ }
+ }
+
private static void addCommand(Path path, float[] current,
char previousCmd, char cmd, float[] val) {
@@ -523,6 +605,5 @@ public class PathParser {
ep1y = ep2y;
}
}
-
}
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f74a356..198333f 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5400,6 +5400,9 @@
<enum name="floatType" value="0" />
<!-- valueFrom and valueTo are integers. -->
<enum name="intType" value="1" />
+ <!-- valueFrom and valueTo are paths defined as strings.
+ This type is used for path morphing in AnimatedVectorDrawable. -->
+ <enum name="pathType" value="2" />
</attr>
</declare-styleable>