Merge "AVD now support path morphing."
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.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 @@
* <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 @@
*/
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 @@
}
/**
+ * 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 @@
}
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 @@
}
}
}
-
- 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 @@
* @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 @@
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 @@
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 @@
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 @@
}
}
+ /**
+ * 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 @@
ep1y = ep2y;
}
}
-
}
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 390da2c..4a6311a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5410,6 +5410,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>
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 60177af8f..3773a9b 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -1040,67 +1040,98 @@
}
}
- /* Setters and Getters */
+ /* Setters and Getters, mostly used by animator from AnimatedVectorDrawable. */
+ @SuppressWarnings("unused")
+ public PathParser.PathDataNode[] getPathData() {
+ return mNode;
+ }
+
+ @SuppressWarnings("unused")
+ public void setPathData(PathParser.PathDataNode[] node) {
+ if (!PathParser.canMorph(mNode, node)) {
+ // This should not happen in the middle of animation.
+ mNode = PathParser.deepCopyNodes(node);
+ } else {
+ PathParser.updateNodes(mNode, node);
+ }
+ }
+
+ @SuppressWarnings("unused")
int getStroke() {
return mStrokeColor;
}
+ @SuppressWarnings("unused")
void setStroke(int strokeColor) {
mStrokeColor = strokeColor;
}
+ @SuppressWarnings("unused")
float getStrokeWidth() {
return mStrokeWidth;
}
+ @SuppressWarnings("unused")
void setStrokeWidth(float strokeWidth) {
mStrokeWidth = strokeWidth;
}
+ @SuppressWarnings("unused")
float getStrokeOpacity() {
return mStrokeOpacity;
}
+ @SuppressWarnings("unused")
void setStrokeOpacity(float strokeOpacity) {
mStrokeOpacity = strokeOpacity;
}
+ @SuppressWarnings("unused")
int getFill() {
return mFillColor;
}
+ @SuppressWarnings("unused")
void setFill(int fillColor) {
mFillColor = fillColor;
}
+ @SuppressWarnings("unused")
float getFillOpacity() {
return mFillOpacity;
}
+ @SuppressWarnings("unused")
void setFillOpacity(float fillOpacity) {
mFillOpacity = fillOpacity;
}
+ @SuppressWarnings("unused")
float getTrimPathStart() {
return mTrimPathStart;
}
+ @SuppressWarnings("unused")
void setTrimPathStart(float trimPathStart) {
mTrimPathStart = trimPathStart;
}
+ @SuppressWarnings("unused")
float getTrimPathEnd() {
return mTrimPathEnd;
}
+ @SuppressWarnings("unused")
void setTrimPathEnd(float trimPathEnd) {
mTrimPathEnd = trimPathEnd;
}
+ @SuppressWarnings("unused")
float getTrimPathOffset() {
return mTrimPathOffset;
}
+ @SuppressWarnings("unused")
void setTrimPathOffset(float trimPathOffset) {
mTrimPathOffset = trimPathOffset;
}
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation05.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation05.xml
new file mode 100644
index 0000000..7012f4b
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation05.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:ordering="sequentially" >
+
+ <objectAnimator
+ android:duration="3000"
+ android:propertyName="pathData"
+ android:valueFrom="@string/triangle"
+ android:valueTo="@string/rectangle"
+ android:valueType="pathType"/>
+
+ <objectAnimator
+ android:duration="3000"
+ android:propertyName="pathData"
+ android:valueFrom="@string/rectangle2"
+ android:valueTo="@string/equal2"
+ android:valueType="pathType"/>
+
+</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 0900b7c..b37b19f 100644
--- a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
@@ -19,9 +19,13 @@
<target
android:name="pie1"
android:animation="@anim/trim_path_animation01" />
+
<target
android:name="v"
android:animation="@anim/trim_path_animation02" />
+ <target
+ android:name="v"
+ android:animation="@anim/trim_path_animation05" />
<target
android:name="rotationGroup"
@@ -36,4 +40,4 @@
android:name="rotationGroup"
android:animation="@anim/trim_path_animation04" />
-</animated-vector>
\ No newline at end of file
+</animated-vector>
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml
index e28ec41..a212def 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml
@@ -32,27 +32,21 @@
android:name="pie1"
android:fill="#00000000"
android:pathData="M300,70 a230,230 0 1,0 1,0 z"
- android:stroke="#FF00FF00"
+ android:stroke="#FF777777"
android:strokeWidth="70"
android:trimPathEnd=".75"
android:trimPathOffset="0"
android:trimPathStart="0" />
<path
android:name="v"
- android:fill="#FF00FF00"
- android:pathData="M300,70 l 0,-70 70,70 -70,70z" />
+ android:fill="#000000"
+ android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
<group
android:name="translateToCenterGroup"
android:rotation="0.0"
android:translateX="200.0"
android:translateY="200.0" >
- <path
- android:name="twoLines"
- android:pathData="@string/twoLinePathData"
- android:stroke="#FFFF0000"
- android:strokeWidth="20" />
-
<group
android:name="rotationGroup2"
android:pivotX="0.0"
@@ -61,7 +55,7 @@
<path
android:name="twoLines1"
android:pathData="@string/twoLinePathData"
- android:stroke="#FF00FF00"
+ android:stroke="#FFFF0000"
android:strokeWidth="20" />
<group
diff --git a/tests/VectorDrawableTest/res/values/strings.xml b/tests/VectorDrawableTest/res/values/strings.xml
index b49a1aa..6ae3d7f 100644
--- a/tests/VectorDrawableTest/res/values/strings.xml
+++ b/tests/VectorDrawableTest/res/values/strings.xml
@@ -16,4 +16,11 @@
<resources>
<string name="twoLinePathData" >"M 0,0 v 100 M 0,0 h 100"</string>
+
+ <string name="triangle" > "M300,70 l 0,-70 70,70 0,0 -70,70z"</string>
+ <string name="rectangle" >"M300,70 l 0,-70 70,0 0,140 -70,0 z"</string>
+
+ <string name="rectangle2" >"M300,70 l 0,-70 70,0 0,70z M300,70 l 70,0 0,70 -70,0z"</string>
+ <string name="equal2" > "M300,35 l 0,-35 70,0 0,35z M300,105 l 70,0 0,35 -70,0z"</string>
+
</resources>