summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorztenghui <ztenghui@google.com>2014-06-17 09:54:45 -0700
committerztenghui <ztenghui@google.com>2014-06-19 10:56:28 -0700
commitcf4832f69c8786b098ce18c24319021f8cd6733a (patch)
treee8fd8df89d7ff6a9a6de5a4e9bd03ee9e88c7a8c
parent150bfcd73d53cd824b4ab20161a3d87710ce259b (diff)
downloadframeworks_base-cf4832f69c8786b098ce18c24319021f8cd6733a.zip
frameworks_base-cf4832f69c8786b098ce18c24319021f8cd6733a.tar.gz
frameworks_base-cf4832f69c8786b098ce18c24319021f8cd6733a.tar.bz2
Add path support into xml files for PathInterpolator and ObjectAnimator.
The test case is showing that AnimatedVectorDrawable is able to use path to define time interpolator and object movement now. Change-Id: If3c0418265d0fd762c8f5f0bb8c39cce3ad34ef3
-rw-r--r--api/current.txt2
-rw-r--r--core/java/android/animation/AnimatorInflater.java164
-rw-r--r--core/java/android/util/PathParser.java528
-rw-r--r--core/java/android/view/animation/PathInterpolator.java52
-rw-r--r--core/res/res/values/attrs.xml14
-rw-r--r--core/res/res/values/public.xml2
-rw-r--r--graphics/java/android/graphics/drawable/VectorDrawable.java496
-rw-r--r--tests/VectorDrawableTest/res/anim/trim_path_animation03.xml4
-rw-r--r--tests/VectorDrawableTest/res/anim/trim_path_animation04.xml8
-rw-r--r--tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml3
-rw-r--r--tests/VectorDrawableTest/res/interpolator/custom_path_interpolator.xml2
11 files changed, 705 insertions, 570 deletions
diff --git a/api/current.txt b/api/current.txt
index 65589d4..e85e1d3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -937,6 +937,8 @@ package android {
field public static final int progressTintMode = 16843878; // 0x1010466
field public static final int prompt = 16843131; // 0x101017b
field public static final int propertyName = 16843489; // 0x10102e1
+ field public static final int propertyXName = 16843894; // 0x1010476
+ field public static final int propertyYName = 16843895; // 0x1010477
field public static final int protectionLevel = 16842761; // 0x1010009
field public static final int publicKey = 16843686; // 0x10103a6
field public static final int queryActionMsg = 16843227; // 0x10101db
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 06f5aca..39fcf73 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -17,11 +17,13 @@ package android.animation;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
-import android.content.res.Resources.NotFoundException;
+import android.graphics.Path;
import android.util.AttributeSet;
+import android.util.PathParser;
import android.util.StateSet;
import android.util.TypedValue;
import android.util.Xml;
@@ -158,7 +160,7 @@ public class AnimatorInflater {
int stateIndex = 0;
for (int i = 0; i < attributeCount; i++) {
int attrName = attributeSet.getAttributeNameResource(i);
- if (attrName == com.android.internal.R.attr.animation) {
+ if (attrName == R.attr.animation) {
animator = loadAnimator(context,
attributeSet.getAttributeResourceValue(i, 0));
} else {
@@ -186,36 +188,43 @@ public class AnimatorInflater {
}
}
+ /**
+ * @param anim Null if this is a ValueAnimator, otherwise this is an
+ * ObjectAnimator
+ * @param arrayAnimator Incoming typed array for Animator's attributes.
+ * @param arrayObjectAnimator Incoming typed array for Object Animator's
+ * attributes.
+ */
+ private static void parseAnimatorFromTypeArray(ValueAnimator anim,
+ TypedArray arrayAnimator, TypedArray arrayObjectAnimator) {
+ long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
- private static void parseAnimatorFromTypeArray(ValueAnimator anim, TypedArray a) {
- long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 300);
-
- long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
+ long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
- int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType,
+ int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType,
VALUE_TYPE_FLOAT);
if (anim == null) {
anim = new ValueAnimator();
}
- TypeEvaluator evaluator = null;
- int valueFromIndex = com.android.internal.R.styleable.Animator_valueFrom;
- int valueToIndex = com.android.internal.R.styleable.Animator_valueTo;
+ TypeEvaluator evaluator = null;
+ int valueFromIndex = R.styleable.Animator_valueFrom;
+ int valueToIndex = R.styleable.Animator_valueTo;
boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
- TypedValue tvFrom = a.peekValue(valueFromIndex);
+ TypedValue tvFrom = arrayAnimator.peekValue(valueFromIndex);
boolean hasFrom = (tvFrom != null);
int fromType = hasFrom ? tvFrom.type : 0;
- TypedValue tvTo = a.peekValue(valueToIndex);
+ TypedValue tvTo = arrayAnimator.peekValue(valueToIndex);
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))) {
+ (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();
@@ -226,15 +235,15 @@ public class AnimatorInflater {
float valueTo;
if (hasFrom) {
if (fromType == TypedValue.TYPE_DIMENSION) {
- valueFrom = a.getDimension(valueFromIndex, 0f);
+ valueFrom = arrayAnimator.getDimension(valueFromIndex, 0f);
} else {
- valueFrom = a.getFloat(valueFromIndex, 0f);
+ valueFrom = arrayAnimator.getFloat(valueFromIndex, 0f);
}
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
- valueTo = a.getDimension(valueToIndex, 0f);
+ valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
} else {
- valueTo = a.getFloat(valueToIndex, 0f);
+ valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
}
anim.setFloatValues(valueFrom, valueTo);
} else {
@@ -242,9 +251,9 @@ public class AnimatorInflater {
}
} else {
if (toType == TypedValue.TYPE_DIMENSION) {
- valueTo = a.getDimension(valueToIndex, 0f);
+ valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
} else {
- valueTo = a.getFloat(valueToIndex, 0f);
+ valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
}
anim.setFloatValues(valueTo);
}
@@ -253,21 +262,21 @@ public class AnimatorInflater {
int valueTo;
if (hasFrom) {
if (fromType == TypedValue.TYPE_DIMENSION) {
- valueFrom = (int) a.getDimension(valueFromIndex, 0f);
+ valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f);
} else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
- valueFrom = a.getColor(valueFromIndex, 0);
+ valueFrom = arrayAnimator.getColor(valueFromIndex, 0);
} else {
- valueFrom = a.getInt(valueFromIndex, 0);
+ valueFrom = arrayAnimator.getInt(valueFromIndex, 0);
}
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
- valueTo = (int) a.getDimension(valueToIndex, 0f);
+ valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
} else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
- valueTo = a.getColor(valueToIndex, 0);
+ valueTo = arrayAnimator.getColor(valueToIndex, 0);
} else {
- valueTo = a.getInt(valueToIndex, 0);
+ valueTo = arrayAnimator.getInt(valueToIndex, 0);
}
anim.setIntValues(valueFrom, valueTo);
} else {
@@ -276,12 +285,12 @@ public class AnimatorInflater {
} else {
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
- valueTo = (int) a.getDimension(valueToIndex, 0f);
+ valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
} else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
- valueTo = a.getColor(valueToIndex, 0);
+ (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ valueTo = arrayAnimator.getColor(valueToIndex, 0);
} else {
- valueTo = a.getInt(valueToIndex, 0);
+ valueTo = arrayAnimator.getInt(valueToIndex, 0);
}
anim.setIntValues(valueTo);
}
@@ -291,18 +300,59 @@ public class AnimatorInflater {
anim.setDuration(duration);
anim.setStartDelay(startDelay);
- if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) {
+ if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) {
anim.setRepeatCount(
- a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0));
+ arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0));
}
- if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) {
+ if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) {
anim.setRepeatMode(
- a.getInt(com.android.internal.R.styleable.Animator_repeatMode,
+ 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)
@@ -338,11 +388,11 @@ public class AnimatorInflater {
anim = new AnimatorSet();
TypedArray a;
if (theme != null) {
- a = theme.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AnimatorSet, 0, 0);
+ a = theme.obtainStyledAttributes(attrs, R.styleable.AnimatorSet, 0, 0);
} else {
- a = res.obtainAttributes(attrs, com.android.internal.R.styleable.AnimatorSet);
+ a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
}
- int ordering = a.getInt(com.android.internal.R.styleable.AnimatorSet_ordering,
+ int ordering = a.getInt(R.styleable.AnimatorSet_ordering,
TOGETHER);
createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering);
a.recycle();
@@ -380,19 +430,6 @@ public class AnimatorInflater {
loadAnimator(res, theme, attrs, anim);
- TypedArray a;
- if (theme != null) {
- a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyAnimator, 0, 0);
- } else {
- a = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
- }
-
- String propertyName = a.getString(R.styleable.PropertyAnimator_propertyName);
-
- anim.setPropertyName(propertyName);
-
- a.recycle();
-
return anim;
}
@@ -402,26 +439,41 @@ public class AnimatorInflater {
*
* @param res The resources
* @param attrs The set of attributes holding the animation parameters
+ * @param anim Null if this is a ValueAnimator, otherwise this is an
+ * ObjectAnimator
*/
private static ValueAnimator loadAnimator(Resources res, Theme theme,
AttributeSet attrs, ValueAnimator anim)
throws NotFoundException {
- TypedArray a;
+ TypedArray arrayAnimator = null;
+ TypedArray arrayObjectAnimator = null;
+
if (theme != null) {
- a = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
+ arrayAnimator = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
} else {
- a = res.obtainAttributes(attrs, R.styleable.Animator);
+ arrayAnimator = res.obtainAttributes(attrs, R.styleable.Animator);
}
- parseAnimatorFromTypeArray(anim, a);
+ // If anim is not null, then it is an object animator.
+ if (anim != null) {
+ if (theme != null) {
+ arrayObjectAnimator = theme.obtainStyledAttributes(attrs,
+ R.styleable.PropertyAnimator, 0, 0);
+ } else {
+ arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
+ }
+ }
+ parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator);
final int resID =
- a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
+ arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
if (resID > 0) {
anim.setInterpolator(AnimationUtils.loadInterpolator(res, theme, resID));
}
- a.recycle();
+
+ arrayAnimator.recycle();
+ arrayObjectAnimator.recycle();
return anim;
}
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
new file mode 100644
index 0000000..f90ce51
--- /dev/null
+++ b/core/java/android/util/PathParser.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.util;
+
+import android.graphics.Path;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class PathParser {
+ static final String LOGTAG = PathParser.class.getSimpleName();
+
+ /**
+ * @param pathData The string representing a path, the same as "d" string in svg file.
+ * @return the generated Path object.
+ */
+ public static Path createPathFromPathData(String pathData) {
+ Path path = new Path();
+ PathDataNode[] nodes = createNodesFromPathData(pathData);
+ if (nodes != null) {
+ PathDataNode.nodesToPath(nodes, path);
+ return path;
+ }
+ return null;
+ }
+
+ /**
+ * @param pathData The string representing a path, the same as "d" string in svg file.
+ * @return an array of the PathDataNode.
+ */
+ public static PathDataNode[] createNodesFromPathData(String pathData) {
+ int start = 0;
+ int end = 1;
+
+ ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
+ while (end < pathData.length()) {
+ end = nextStart(pathData, end);
+ String s = pathData.substring(start, end);
+ float[] val = getFloats(s);
+ addNode(list, s.charAt(0), val);
+
+ start = end;
+ end++;
+ }
+ if ((end - start) == 1 && start < pathData.length()) {
+ addNode(list, pathData.charAt(start), new float[0]);
+ }
+ return list.toArray(new PathDataNode[list.size()]);
+ }
+
+ private static int nextStart(String s, int end) {
+ char c;
+
+ while (end < s.length()) {
+ c = s.charAt(end);
+ if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
+ return end;
+ }
+ end++;
+ }
+ return end;
+ }
+
+ private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) {
+ list.add(new PathDataNode(cmd, val));
+ }
+
+
+ /**
+ * Parse the floats in the string.
+ * This is an optimized version of parseFloat(s.split(",|\\s"));
+ *
+ * @param s the string containing a command and list of floats
+ * @return array of floats
+ */
+ private static float[] getFloats(String s) {
+ if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
+ return new float[0];
+ }
+ try {
+ float[] tmp = new float[s.length()];
+ int count = 0;
+ int pos = 1, end;
+ while ((end = extract(s, pos)) >= 0) {
+ if (pos < end) {
+ tmp[count++] = Float.parseFloat(s.substring(pos, end));
+ }
+ pos = end + 1;
+ }
+ // handle the final float if there is one
+ if (pos < s.length()) {
+ tmp[count++] = Float.parseFloat(s.substring(pos, s.length()));
+ }
+ return Arrays.copyOf(tmp, count);
+ } catch (NumberFormatException e){
+ Log.e(LOGTAG,"error in parsing \""+s+"\"");
+ throw e;
+ }
+ }
+
+ /**
+ * Calculate the position of the next comma or space
+ * @param s the string to search
+ * @param start the position to start searching
+ * @return the position of the next comma or space or -1 if none found
+ */
+ private static int extract(String s, int start) {
+ int space = s.indexOf(' ', start);
+ int comma = s.indexOf(',', start);
+ if (space == -1) {
+ return comma;
+ }
+ if (comma == -1) {
+ return space;
+ }
+ return (comma > space) ? space : comma;
+ }
+
+ public static class PathDataNode {
+ private char mType;
+ private float[] mParams;
+
+ private PathDataNode(char type, float[] params) {
+ mType = type;
+ mParams = params;
+ }
+
+ private PathDataNode(PathDataNode n) {
+ mType = n.mType;
+ mParams = Arrays.copyOf(n.mParams, n.mParams.length);
+ }
+
+ public static void nodesToPath(PathDataNode[] node, Path path) {
+ float[] current = new float[4];
+ char previousCommand = 'm';
+ for (int i = 0; i < node.length; i++) {
+ addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
+ previousCommand = node[i].mType;
+ }
+ }
+
+ private static void addCommand(Path path, float[] current,
+ char previousCmd, char cmd, float[] val) {
+
+ int incr = 2;
+ float currentX = current[0];
+ float currentY = current[1];
+ float ctrlPointX = current[2];
+ float ctrlPointY = current[3];
+ float reflectiveCtrlPointX;
+ float reflectiveCtrlPointY;
+
+ switch (cmd) {
+ case 'z':
+ case 'Z':
+ path.close();
+ return;
+ case 'm':
+ case 'M':
+ case 'l':
+ case 'L':
+ case 't':
+ case 'T':
+ incr = 2;
+ break;
+ case 'h':
+ case 'H':
+ case 'v':
+ case 'V':
+ incr = 1;
+ break;
+ case 'c':
+ case 'C':
+ incr = 6;
+ break;
+ case 's':
+ case 'S':
+ case 'q':
+ case 'Q':
+ incr = 4;
+ break;
+ case 'a':
+ case 'A':
+ incr = 7;
+ break;
+ }
+ for (int k = 0; k < val.length; k += incr) {
+ switch (cmd) {
+ case 'm': // moveto - Start a new sub-path (relative)
+ path.rMoveTo(val[k + 0], val[k + 1]);
+ currentX += val[k + 0];
+ currentY += val[k + 1];
+ break;
+ case 'M': // moveto - Start a new sub-path
+ path.moveTo(val[k + 0], val[k + 1]);
+ currentX = val[k + 0];
+ currentY = val[k + 1];
+ break;
+ case 'l': // lineto - Draw a line from the current point (relative)
+ path.rLineTo(val[k + 0], val[k + 1]);
+ currentX += val[k + 0];
+ currentY += val[k + 1];
+ break;
+ case 'L': // lineto - Draw a line from the current point
+ path.lineTo(val[k + 0], val[k + 1]);
+ currentX = val[k + 0];
+ currentY = val[k + 1];
+ break;
+ case 'z': // closepath - Close the current subpath
+ case 'Z': // closepath - Close the current subpath
+ path.close();
+ break;
+ case 'h': // horizontal lineto - Draws a horizontal line (relative)
+ path.rLineTo(val[k + 0], 0);
+ currentX += val[k + 0];
+ break;
+ case 'H': // horizontal lineto - Draws a horizontal line
+ path.lineTo(val[k + 0], currentY);
+ currentX = val[k + 0];
+ break;
+ case 'v': // vertical lineto - Draws a vertical line from the current point (r)
+ path.rLineTo(0, val[k + 0]);
+ currentY += val[k + 0];
+ break;
+ case 'V': // vertical lineto - Draws a vertical line from the current point
+ path.lineTo(currentX, val[k + 0]);
+ currentY = val[k + 0];
+ break;
+ case 'c': // curveto - Draws a cubic Bézier curve (relative)
+ path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+ val[k + 4], val[k + 5]);
+
+ ctrlPointX = currentX + val[k + 2];
+ ctrlPointY = currentY + val[k + 3];
+ currentX += val[k + 4];
+ currentY += val[k + 5];
+
+ break;
+ case 'C': // curveto - Draws a cubic Bézier curve
+ path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+ val[k + 4], val[k + 5]);
+ currentX = val[k + 4];
+ currentY = val[k + 5];
+ ctrlPointX = val[k + 2];
+ ctrlPointY = val[k + 3];
+ break;
+ case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
+ reflectiveCtrlPointX = 0;
+ reflectiveCtrlPointY = 0;
+ if (previousCmd == 'c' || previousCmd == 's'
+ || previousCmd == 'C' || previousCmd == 'S') {
+ reflectiveCtrlPointX = currentX - ctrlPointX;
+ reflectiveCtrlPointY = currentY - ctrlPointY;
+ }
+ path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1],
+ val[k + 2], val[k + 3]);
+
+ ctrlPointX = currentX + val[k + 0];
+ ctrlPointY = currentY + val[k + 1];
+ currentX += val[k + 2];
+ currentY += val[k + 3];
+ break;
+ case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
+ reflectiveCtrlPointX = currentX;
+ reflectiveCtrlPointY = currentY;
+ if (previousCmd == 'c' || previousCmd == 's'
+ || previousCmd == 'C' || previousCmd == 'S') {
+ reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+ reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+ }
+ path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+ ctrlPointX = val[k + 0];
+ ctrlPointY = val[k + 1];
+ currentX = val[k + 2];
+ currentY = val[k + 3];
+ break;
+ case 'q': // Draws a quadratic Bézier (relative)
+ path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+ ctrlPointX = currentX + val[k + 0];
+ ctrlPointY = currentY + val[k + 1];
+ currentX += val[k + 2];
+ currentY += val[k + 3];
+ break;
+ case 'Q': // Draws a quadratic Bézier
+ path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+ ctrlPointX = val[k + 0];
+ ctrlPointY = val[k + 1];
+ currentX = val[k + 2];
+ currentY = val[k + 3];
+ break;
+ case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
+ reflectiveCtrlPointX = 0;
+ reflectiveCtrlPointY = 0;
+ if (previousCmd == 'q' || previousCmd == 't'
+ || previousCmd == 'Q' || previousCmd == 'T') {
+ reflectiveCtrlPointX = currentX - ctrlPointX;
+ reflectiveCtrlPointY = currentY - ctrlPointY;
+ }
+ path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1]);
+ ctrlPointX = currentX + reflectiveCtrlPointX;
+ ctrlPointY = currentY + reflectiveCtrlPointY;
+ currentX += val[k + 0];
+ currentY += val[k + 1];
+ break;
+ case 'T': // Draws a quadratic Bézier curve (reflective control point)
+ reflectiveCtrlPointX = currentX;
+ reflectiveCtrlPointY = currentY;
+ if (previousCmd == 'q' || previousCmd == 't'
+ || previousCmd == 'Q' || previousCmd == 'T') {
+ reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+ reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+ }
+ path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ val[k + 0], val[k + 1]);
+ ctrlPointX = reflectiveCtrlPointX;
+ ctrlPointY = reflectiveCtrlPointY;
+ currentX = val[k + 0];
+ currentY = val[k + 1];
+ break;
+ case 'a': // Draws an elliptical arc
+ // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+ drawArc(path,
+ currentX,
+ currentY,
+ val[k + 5] + currentX,
+ val[k + 6] + currentY,
+ val[k + 0],
+ val[k + 1],
+ val[k + 2],
+ val[k + 3] != 0,
+ val[k + 4] != 0);
+ currentX += val[k + 5];
+ currentY += val[k + 6];
+ ctrlPointX = currentX;
+ ctrlPointY = currentY;
+ break;
+ case 'A': // Draws an elliptical arc
+ drawArc(path,
+ currentX,
+ currentY,
+ val[k + 5],
+ val[k + 6],
+ val[k + 0],
+ val[k + 1],
+ val[k + 2],
+ val[k + 3] != 0,
+ val[k + 4] != 0);
+ currentX = val[k + 5];
+ currentY = val[k + 6];
+ ctrlPointX = currentX;
+ ctrlPointY = currentY;
+ break;
+ }
+ previousCmd = cmd;
+ }
+ current[0] = currentX;
+ current[1] = currentY;
+ current[2] = ctrlPointX;
+ current[3] = ctrlPointY;
+ }
+
+ private static void drawArc(Path p,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ float a,
+ float b,
+ float theta,
+ boolean isMoreThanHalf,
+ boolean isPositiveArc) {
+
+ /* Convert rotation angle from degrees to radians */
+ double thetaD = Math.toRadians(theta);
+ /* Pre-compute rotation matrix entries */
+ double cosTheta = Math.cos(thetaD);
+ double sinTheta = Math.sin(thetaD);
+ /* Transform (x0, y0) and (x1, y1) into unit space */
+ /* using (inverse) rotation, followed by (inverse) scale */
+ double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+ double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+ double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+ double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+
+ /* Compute differences and averages */
+ double dx = x0p - x1p;
+ double dy = y0p - y1p;
+ double xm = (x0p + x1p) / 2;
+ double ym = (y0p + y1p) / 2;
+ /* Solve for intersecting unit circles */
+ double dsq = dx * dx + dy * dy;
+ if (dsq == 0.0) {
+ Log.w(LOGTAG, " Points are coincident");
+ return; /* Points are coincident */
+ }
+ double disc = 1.0 / dsq - 1.0 / 4.0;
+ if (disc < 0.0) {
+ Log.w(LOGTAG, "Points are too far apart " + dsq);
+ float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+ drawArc(p, x0, y0, x1, y1, a * adjust,
+ b * adjust, theta, isMoreThanHalf, isPositiveArc);
+ return; /* Points are too far apart */
+ }
+ double s = Math.sqrt(disc);
+ double sdx = s * dx;
+ double sdy = s * dy;
+ double cx;
+ double cy;
+ if (isMoreThanHalf == isPositiveArc) {
+ cx = xm - sdy;
+ cy = ym + sdx;
+ } else {
+ cx = xm + sdy;
+ cy = ym - sdx;
+ }
+
+ double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+
+ double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+
+ double sweep = (eta1 - eta0);
+ if (isPositiveArc != (sweep >= 0)) {
+ if (sweep > 0) {
+ sweep -= 2 * Math.PI;
+ } else {
+ sweep += 2 * Math.PI;
+ }
+ }
+
+ cx *= a;
+ cy *= b;
+ double tcx = cx;
+ cx = cx * cosTheta - cy * sinTheta;
+ cy = tcx * sinTheta + cy * cosTheta;
+
+ arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+ }
+
+ /**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+ private static void arcToBezier(Path p,
+ double cx,
+ double cy,
+ double a,
+ double b,
+ double e1x,
+ double e1y,
+ double theta,
+ double start,
+ double sweep) {
+ // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+ // and http://www.spaceroots.org/documents/ellipse/node22.html
+
+ // Maximum of 45 degrees per cubic Bezier segment
+ int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
+
+ double eta1 = start;
+ double cosTheta = Math.cos(theta);
+ double sinTheta = Math.sin(theta);
+ double cosEta1 = Math.cos(eta1);
+ double sinEta1 = Math.sin(eta1);
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+ double anglePerSegment = sweep / numSegments;
+ for (int i = 0; i < numSegments; i++) {
+ double eta2 = eta1 + anglePerSegment;
+ double sinEta2 = Math.sin(eta2);
+ double cosEta2 = Math.cos(eta2);
+ double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+ double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+ double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+ double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+ double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+ double alpha =
+ Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+ double q1x = e1x + alpha * ep1x;
+ double q1y = e1y + alpha * ep1y;
+ double q2x = e2x - alpha * ep2x;
+ double q2y = e2y - alpha * ep2y;
+
+ p.cubicTo((float) q1x,
+ (float) q1y,
+ (float) q2x,
+ (float) q2y,
+ (float) e2x,
+ (float) e2y);
+ eta1 = eta2;
+ e1x = e2x;
+ e1y = e2y;
+ ep1x = ep2x;
+ ep1y = ep2y;
+ }
+ }
+
+ }
+}
diff --git a/core/java/android/view/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java
index da12ffb..945ecf0 100644
--- a/core/java/android/view/animation/PathInterpolator.java
+++ b/core/java/android/view/animation/PathInterpolator.java
@@ -21,6 +21,7 @@ import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Path;
import android.util.AttributeSet;
+import android.util.PathParser;
import android.view.InflateException;
import com.android.internal.R;
@@ -102,28 +103,40 @@ public class PathInterpolator implements Interpolator {
}
private void parseInterpolatorFromTypeArray(TypedArray a) {
- if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) {
- throw new InflateException("pathInterpolator requires the controlX1 attribute");
- } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) {
- throw new InflateException("pathInterpolator requires the controlY1 attribute");
- }
- float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0);
- float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0);
+ // If there is pathData defined in the xml file, then the controls points
+ // will be all coming from pathData.
+ if (a.hasValue(R.styleable.PathInterpolator_pathData)) {
+ String pathData = a.getString(R.styleable.PathInterpolator_pathData);
+ Path path = PathParser.createPathFromPathData(pathData);
+ if (path == null) {
+ throw new InflateException("The path is null, which is created"
+ + " from " + pathData);
+ }
+ initPath(path);
+ } else {
+ if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) {
+ throw new InflateException("pathInterpolator requires the controlX1 attribute");
+ } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) {
+ throw new InflateException("pathInterpolator requires the controlY1 attribute");
+ }
+ float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0);
+ float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0);
- boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2);
- boolean hasY2 = a.hasValue(R.styleable.PathInterpolator_controlY2);
+ boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2);
+ boolean hasY2 = a.hasValue(R.styleable.PathInterpolator_controlY2);
- if (hasX2 != hasY2) {
- throw new InflateException(
- "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
- }
+ if (hasX2 != hasY2) {
+ throw new InflateException(
+ "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
+ }
- if (!hasX2) {
- initQuad(x1, y1);
- } else {
- float x2 = a.getFloat(R.styleable.PathInterpolator_controlX2, 0);
- float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0);
- initCubic(x1, y1, x2, y2);
+ if (!hasX2) {
+ initQuad(x1, y1);
+ } else {
+ float x2 = a.getFloat(R.styleable.PathInterpolator_controlX2, 0);
+ float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0);
+ initCubic(x1, y1, x2, y2);
+ }
}
}
@@ -216,5 +229,4 @@ public class PathInterpolator implements Interpolator {
float endY = mY[endIndex];
return startY + (fraction * (endY - startY));
}
-
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8d8f69e..f74a356 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5250,10 +5250,18 @@
</declare-styleable>
<declare-styleable name="PathInterpolator">
+ <!-- The x coordinate of the first control point of the cubic Bezier -->
<attr name="controlX1" format="float" />
+ <!-- The y coordinate of the first control point of the cubic Bezier -->
<attr name="controlY1" format="float" />
+ <!-- The x coordinate of the second control point of the cubic Bezier -->
<attr name="controlX2" format="float" />
+ <!-- The y coordinate of the second control point of the cubic Bezier -->
<attr name="controlY2" format="float" />
+ <!-- The control points defined as a path.
+ When pathData is defined, then both of the control points of the
+ cubic Bezier will be ignored. -->
+ <attr name="pathData"/>
</declare-styleable>
<!-- ========================== -->
@@ -5403,6 +5411,12 @@
<declare-styleable name="PropertyAnimator">
<!-- Name of the property being animated. -->
<attr name="propertyName" format="string"/>
+ <!-- Name of the property being animated as the X coordinate of the pathData. -->
+ <attr name="propertyXName" format="string"/>
+ <!-- Name of the property being animated as the Y coordinate of the pathData. -->
+ <attr name="propertyYName" format="string"/>
+ <!-- The path used to animate the properties in the ObjectAnimator -->
+ <attr name="pathData"/>
</declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f6ad78b..f73c958 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2207,6 +2207,8 @@
<public type="attr" name="thumbTint" />
<public type="attr" name="thumbTintMode" />
<public type="attr" name="fullBackupOnly" />
+ <public type="attr" name="propertyXName" />
+ <public type="attr" name="propertyYName" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index a1e1f76..b4d1fdc 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -33,6 +33,7 @@ import android.graphics.PorterDuff.Mode;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.PathParser;
import android.util.Xml;
import com.android.internal.R;
@@ -956,7 +957,7 @@ public class VectorDrawable extends Drawable {
}
- static class VPath {
+ private static class VPath {
private int[] mThemeAttrs;
int mStrokeColor = 0;
@@ -974,7 +975,7 @@ public class VectorDrawable extends Drawable {
Paint.Join mStrokeLineJoin = Paint.Join.MITER;
float mStrokeMiterlimit = 4;
- private VNode[] mNode = null;
+ private PathParser.PathDataNode[] mNode = null;
private String mPathName;
public VPath() {
@@ -984,7 +985,7 @@ public class VectorDrawable extends Drawable {
public void toPath(Path path) {
path.reset();
if (mNode != null) {
- VNode.createPath(mNode, path);
+ PathParser.PathDataNode.nodesToPath(mNode, path);
}
}
@@ -1099,7 +1100,8 @@ public class VectorDrawable extends Drawable {
}
if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] == 0) {
- mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
+ mNode = PathParser.createNodesFromPathData(a.getString(
+ R.styleable.VectorDrawablePath_pathData));
}
if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0) {
@@ -1182,7 +1184,8 @@ public class VectorDrawable extends Drawable {
}
if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) {
- mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
+ mNode = PathParser.createNodesFromPathData(a.getString(
+ R.styleable.VectorDrawablePath_pathData));
}
mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
@@ -1218,488 +1221,5 @@ public class VectorDrawable extends Drawable {
mStrokeColor = applyAlpha(mStrokeColor, mStrokeOpacity);
}
}
-
- private static int nextStart(String s, int end) {
- char c;
-
- while (end < s.length()) {
- c = s.charAt(end);
- if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
- return end;
- }
- end++;
- }
- return end;
- }
-
- private void addNode(ArrayList<VectorDrawable.VNode> list, char cmd, float[] val) {
- list.add(new VectorDrawable.VNode(cmd, val));
- }
-
- /**
- * parse the floats in the string
- * this is an optimized version of
- * parseFloat(s.split(",|\\s"));
- *
- * @param s the string containing a command and list of floats
- * @return array of floats
- */
- private static float[] getFloats(String s) {
- if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
- return new float[0];
- }
- try {
- float[] tmp = new float[s.length()];
- int count = 0;
- int pos = 1, end;
- while ((end = extract(s, pos)) >= 0) {
- if (pos < end) {
- tmp[count++] = Float.parseFloat(s.substring(pos, end));
- }
- pos = end + 1;
- }
- // handle the final float if there is one
- if (pos < s.length()) {
- tmp[count++] = Float.parseFloat(s.substring(pos, s.length()));
- }
- return Arrays.copyOf(tmp, count);
- } catch (NumberFormatException e){
- Log.e(LOGTAG,"error in parsing \""+s+"\"");
- throw e;
- }
- }
-
- /**
- * calculate the position of the next comma or space
- * @param s the string to search
- * @param start the position to start searching
- * @return the position of the next comma or space or -1 if none found
- */
- private static int extract(String s, int start) {
- int space = s.indexOf(' ', start);
- int comma = s.indexOf(',', start);
- if (space == -1) {
- return comma;
- }
- if (comma == -1) {
- return space;
- }
- return (comma > space) ? space : comma;
- }
-
- private VectorDrawable.VNode[] parsePath(String value) {
- int start = 0;
- int end = 1;
-
- ArrayList<VectorDrawable.VNode> list = new ArrayList<VectorDrawable.VNode>();
- while (end < value.length()) {
- end = nextStart(value, end);
- String s = value.substring(start, end);
- float[] val = getFloats(s);
- addNode(list, s.charAt(0), val);
-
- start = end;
- end++;
- }
- if ((end - start) == 1 && start < value.length()) {
-
- addNode(list, value.charAt(start), new float[0]);
- }
- return list.toArray(new VectorDrawable.VNode[list.size()]);
- }
- }
-
- private static class VNode {
- private char mType;
- private float[] mParams;
-
- public VNode(char type, float[] params) {
- mType = type;
- mParams = params;
- }
-
- public VNode(VNode n) {
- mType = n.mType;
- mParams = Arrays.copyOf(n.mParams, n.mParams.length);
- }
-
- public static void createPath(VNode[] node, Path path) {
- float[] current = new float[4];
- char previousCommand = 'm';
- for (int i = 0; i < node.length; i++) {
- addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
- previousCommand = node[i].mType;
- }
- }
-
- private static void addCommand(Path path, float[] current,
- char previousCmd, char cmd, float[] val) {
-
- int incr = 2;
- float currentX = current[0];
- float currentY = current[1];
- float ctrlPointX = current[2];
- float ctrlPointY = current[3];
- float reflectiveCtrlPointX;
- float reflectiveCtrlPointY;
-
- switch (cmd) {
- case 'z':
- case 'Z':
- path.close();
- return;
- case 'm':
- case 'M':
- case 'l':
- case 'L':
- case 't':
- case 'T':
- incr = 2;
- break;
- case 'h':
- case 'H':
- case 'v':
- case 'V':
- incr = 1;
- break;
- case 'c':
- case 'C':
- incr = 6;
- break;
- case 's':
- case 'S':
- case 'q':
- case 'Q':
- incr = 4;
- break;
- case 'a':
- case 'A':
- incr = 7;
- break;
- }
- for (int k = 0; k < val.length; k += incr) {
- switch (cmd) {
- case 'm': // moveto - Start a new sub-path (relative)
- path.rMoveTo(val[k + 0], val[k + 1]);
- currentX += val[k + 0];
- currentY += val[k + 1];
- break;
- case 'M': // moveto - Start a new sub-path
- path.moveTo(val[k + 0], val[k + 1]);
- currentX = val[k + 0];
- currentY = val[k + 1];
- break;
- case 'l': // lineto - Draw a line from the current point (relative)
- path.rLineTo(val[k + 0], val[k + 1]);
- currentX += val[k + 0];
- currentY += val[k + 1];
- break;
- case 'L': // lineto - Draw a line from the current point
- path.lineTo(val[k + 0], val[k + 1]);
- currentX = val[k + 0];
- currentY = val[k + 1];
- break;
- case 'z': // closepath - Close the current subpath
- case 'Z': // closepath - Close the current subpath
- path.close();
- break;
- case 'h': // horizontal lineto - Draws a horizontal line (relative)
- path.rLineTo(val[k + 0], 0);
- currentX += val[k + 0];
- break;
- case 'H': // horizontal lineto - Draws a horizontal line
- path.lineTo(val[k + 0], currentY);
- currentX = val[k + 0];
- break;
- case 'v': // vertical lineto - Draws a vertical line from the current point (r)
- path.rLineTo(0, val[k + 0]);
- currentY += val[k + 0];
- break;
- case 'V': // vertical lineto - Draws a vertical line from the current point
- path.lineTo(currentX, val[k + 0]);
- currentY = val[k + 0];
- break;
- case 'c': // curveto - Draws a cubic Bézier curve (relative)
- path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
- val[k + 4], val[k + 5]);
-
- ctrlPointX = currentX + val[k + 2];
- ctrlPointY = currentY + val[k + 3];
- currentX += val[k + 4];
- currentY += val[k + 5];
-
- break;
- case 'C': // curveto - Draws a cubic Bézier curve
- path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
- val[k + 4], val[k + 5]);
- currentX = val[k + 4];
- currentY = val[k + 5];
- ctrlPointX = val[k + 2];
- ctrlPointY = val[k + 3];
- break;
- case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
- reflectiveCtrlPointX = 0;
- reflectiveCtrlPointY = 0;
- if (previousCmd == 'c' || previousCmd == 's'
- || previousCmd == 'C' || previousCmd == 'S') {
- reflectiveCtrlPointX = currentX - ctrlPointX;
- reflectiveCtrlPointY = currentY - ctrlPointY;
- }
- path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1],
- val[k + 2], val[k + 3]);
-
- ctrlPointX = currentX + val[k + 0];
- ctrlPointY = currentY + val[k + 1];
- currentX += val[k + 2];
- currentY += val[k + 3];
- break;
- case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
- reflectiveCtrlPointX = currentX;
- reflectiveCtrlPointY = currentY;
- if (previousCmd == 'c' || previousCmd == 's'
- || previousCmd == 'C' || previousCmd == 'S') {
- reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
- reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
- }
- path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
- ctrlPointX = val[k + 0];
- ctrlPointY = val[k + 1];
- currentX = val[k + 2];
- currentY = val[k + 3];
- break;
- case 'q': // Draws a quadratic Bézier (relative)
- path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
- ctrlPointX = currentX + val[k + 0];
- ctrlPointY = currentY + val[k + 1];
- currentX += val[k + 2];
- currentY += val[k + 3];
- break;
- case 'Q': // Draws a quadratic Bézier
- path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
- ctrlPointX = val[k + 0];
- ctrlPointY = val[k + 1];
- currentX = val[k + 2];
- currentY = val[k + 3];
- break;
- case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
- reflectiveCtrlPointX = 0;
- reflectiveCtrlPointY = 0;
- if (previousCmd == 'q' || previousCmd == 't'
- || previousCmd == 'Q' || previousCmd == 'T') {
- reflectiveCtrlPointX = currentX - ctrlPointX;
- reflectiveCtrlPointY = currentY - ctrlPointY;
- }
- path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1]);
- ctrlPointX = currentX + reflectiveCtrlPointX;
- ctrlPointY = currentY + reflectiveCtrlPointY;
- currentX += val[k + 0];
- currentY += val[k + 1];
- break;
- case 'T': // Draws a quadratic Bézier curve (reflective control point)
- reflectiveCtrlPointX = currentX;
- reflectiveCtrlPointY = currentY;
- if (previousCmd == 'q' || previousCmd == 't'
- || previousCmd == 'Q' || previousCmd == 'T') {
- reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
- reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
- }
- path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1]);
- ctrlPointX = reflectiveCtrlPointX;
- ctrlPointY = reflectiveCtrlPointY;
- currentX = val[k + 0];
- currentY = val[k + 1];
- break;
- case 'a': // Draws an elliptical arc
- // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
- drawArc(path,
- currentX,
- currentY,
- val[k + 5] + currentX,
- val[k + 6] + currentY,
- val[k + 0],
- val[k + 1],
- val[k + 2],
- val[k + 3] != 0,
- val[k + 4] != 0);
- currentX += val[k + 5];
- currentY += val[k + 6];
- ctrlPointX = currentX;
- ctrlPointY = currentY;
- break;
- case 'A': // Draws an elliptical arc
- drawArc(path,
- currentX,
- currentY,
- val[k + 5],
- val[k + 6],
- val[k + 0],
- val[k + 1],
- val[k + 2],
- val[k + 3] != 0,
- val[k + 4] != 0);
- currentX = val[k + 5];
- currentY = val[k + 6];
- ctrlPointX = currentX;
- ctrlPointY = currentY;
- break;
- }
- previousCmd = cmd;
- }
- current[0] = currentX;
- current[1] = currentY;
- current[2] = ctrlPointX;
- current[3] = ctrlPointY;
- }
-
- private static void drawArc(Path p,
- float x0,
- float y0,
- float x1,
- float y1,
- float a,
- float b,
- float theta,
- boolean isMoreThanHalf,
- boolean isPositiveArc) {
-
- /* Convert rotation angle from degrees to radians */
- double thetaD = Math.toRadians(theta);
- /* Pre-compute rotation matrix entries */
- double cosTheta = Math.cos(thetaD);
- double sinTheta = Math.sin(thetaD);
- /* Transform (x0, y0) and (x1, y1) into unit space */
- /* using (inverse) rotation, followed by (inverse) scale */
- double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
- double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
- double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
- double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
-
- /* Compute differences and averages */
- double dx = x0p - x1p;
- double dy = y0p - y1p;
- double xm = (x0p + x1p) / 2;
- double ym = (y0p + y1p) / 2;
- /* Solve for intersecting unit circles */
- double dsq = dx * dx + dy * dy;
- if (dsq == 0.0) {
- Log.w(LOGTAG, " Points are coincident");
- return; /* Points are coincident */
- }
- double disc = 1.0 / dsq - 1.0 / 4.0;
- if (disc < 0.0) {
- Log.w(LOGTAG, "Points are too far apart " + dsq);
- float adjust = (float) (Math.sqrt(dsq) / 1.99999);
- drawArc(p, x0, y0, x1, y1, a * adjust,
- b * adjust, theta, isMoreThanHalf, isPositiveArc);
- return; /* Points are too far apart */
- }
- double s = Math.sqrt(disc);
- double sdx = s * dx;
- double sdy = s * dy;
- double cx;
- double cy;
- if (isMoreThanHalf == isPositiveArc) {
- cx = xm - sdy;
- cy = ym + sdx;
- } else {
- cx = xm + sdy;
- cy = ym - sdx;
- }
-
- double eta0 = Math.atan2((y0p - cy), (x0p - cx));
-
- double eta1 = Math.atan2((y1p - cy), (x1p - cx));
-
- double sweep = (eta1 - eta0);
- if (isPositiveArc != (sweep >= 0)) {
- if (sweep > 0) {
- sweep -= 2 * Math.PI;
- } else {
- sweep += 2 * Math.PI;
- }
- }
-
- cx *= a;
- cy *= b;
- double tcx = cx;
- cx = cx * cosTheta - cy * sinTheta;
- cy = tcx * sinTheta + cy * cosTheta;
-
- arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
- }
-
- /**
- * Converts an arc to cubic Bezier segments and records them in p.
- *
- * @param p The target for the cubic Bezier segments
- * @param cx The x coordinate center of the ellipse
- * @param cy The y coordinate center of the ellipse
- * @param a The radius of the ellipse in the horizontal direction
- * @param b The radius of the ellipse in the vertical direction
- * @param e1x E(eta1) x coordinate of the starting point of the arc
- * @param e1y E(eta2) y coordinate of the starting point of the arc
- * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
- * @param start The start angle of the arc on the ellipse
- * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
- */
- private static void arcToBezier(Path p,
- double cx,
- double cy,
- double a,
- double b,
- double e1x,
- double e1y,
- double theta,
- double start,
- double sweep) {
- // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
- // and http://www.spaceroots.org/documents/ellipse/node22.html
-
- // Maximum of 45 degrees per cubic Bezier segment
- int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
-
- double eta1 = start;
- double cosTheta = Math.cos(theta);
- double sinTheta = Math.sin(theta);
- double cosEta1 = Math.cos(eta1);
- double sinEta1 = Math.sin(eta1);
- double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
- double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
-
- double anglePerSegment = sweep / numSegments;
- for (int i = 0; i < numSegments; i++) {
- double eta2 = eta1 + anglePerSegment;
- double sinEta2 = Math.sin(eta2);
- double cosEta2 = Math.cos(eta2);
- double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
- double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
- double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
- double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
- double tanDiff2 = Math.tan((eta2 - eta1) / 2);
- double alpha =
- Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
- double q1x = e1x + alpha * ep1x;
- double q1y = e1y + alpha * ep1y;
- double q2x = e2x - alpha * ep2x;
- double q2y = e2y - alpha * ep2y;
-
- p.cubicTo((float) q1x,
- (float) q1y,
- (float) q2x,
- (float) q2y,
- (float) e2x,
- (float) e2y);
- eta1 = eta2;
- e1x = e2x;
- e1y = e2y;
- ep1x = ep2x;
- ep1y = ep2y;
- }
- }
-
}
}
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml
index 72beba2..0c1073e 100644
--- a/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml
@@ -21,6 +21,8 @@
android:duration="6000"
android:propertyName="rotation"
android:valueFrom="0"
- android:valueTo="360"/>
+ android:valueTo="360"
+ android:interpolator="@interpolator/custom_path_interpolator"
+ />
</set> \ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml
index ff86668..4d0aae1 100644
--- a/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml
@@ -16,11 +16,9 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android" >
-
<objectAnimator
android:duration="9000"
- android:propertyName="rotation"
- android:valueFrom="0"
- android:valueTo="360"/>
-
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="m0,0 q 150, 300 150, 0 t 150, 0, t 150, 0 t -150 0 t -150 0 t -150 0 z" />
</set> \ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
index b8681b6..0900b7c 100644
--- a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
@@ -32,5 +32,8 @@
<target
android:name="rotationGroupBlue"
android:animation="@anim/trim_path_animation03" />
+ <target
+ android:name="rotationGroup"
+ android:animation="@anim/trim_path_animation04" />
</animated-vector> \ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator.xml b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator.xml
new file mode 100644
index 0000000..0cffa0a
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator.xml
@@ -0,0 +1,2 @@
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="m0,0q0.4,0.05 0.6,0.3t0.3,0.3l0.1,0.4" /> \ No newline at end of file