AVD now support path morphing.
Basically extended the ValueAnimator to support a new type: pathType. Add the PathDataEvaluator internally to interpolate path data. Update test to show the path morphing. Change-Id: I89db0199cbc12e3041790a6115f3f50b80213cdb
This commit is contained in:
@@ -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
|
||||
*
|
||||
@@ -188,6 +193,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
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1019,67 +1019,98 @@ public class VectorDrawable extends Drawable {
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
35
tests/VectorDrawableTest/res/anim/trim_path_animation05.xml
Normal file
35
tests/VectorDrawableTest/res/anim/trim_path_animation05.xml
Normal file
@@ -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>
|
||||
@@ -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>
|
||||
</animated-vector>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user