This class uses {@link android.animation.ObjectAnimator} and
{@link android.animation.AnimatorSet} to animate the properties of a
{@link android.graphics.drawable.VectorDrawable} to create an animated drawable.
AnimatedVectorDrawable are normally defined as 3 separate XML files.
First is the XML file for {@link android.graphics.drawable.VectorDrawable}.
Note that we allow the animation happen on the group's attributes and path's
attributes, which requires they are uniquely named in this xml file. Groups
and paths without animations do not need names.
Here is a simple VectorDrawable in this vectordrawable.xml file.
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="64dp"
android:width="64dp"
android:viewportHeight="600"
android:viewportWidth="600" >
<group
android:name="rotationGroup"
android:pivotX="300.0"
android:pivotY="300.0"
android:rotation="45.0" >
<path
android:name="v"
android:fillColor="#000000"
android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
</group>
</vector>
Second is the AnimatedVectorDrawable's xml file, which defines the target
VectorDrawable, the target paths and groups to animate, the properties of the
path and group to animate and the animations defined as the ObjectAnimators
or AnimatorSets.
Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/vectordrawable" >
<target
android:name="rotationGroup"
android:animation="@anim/rotation" />
<target
android:name="v"
android:animation="@anim/path_morph" />
</animated-vector>
Last is the Animator xml file, which is the same as a normal ObjectAnimator
or AnimatorSet.
To complete this example, here are the 2 animator files used in avd.xml:
rotation.xml and path_morph.xml.
Here is the rotation.xml, which will rotate the target group for 360 degrees.
<objectAnimator
android:duration="6000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360" />
Here is the path_morph.xml, which will morph the path from one shape to
the other. Note that the paths must be compatible for morphing.
In more details, the paths should have exact same length of commands , and
exact same length of parameters for each commands.
Note that the path string are better stored in strings.xml for reusing.
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="3000"
android:propertyName="pathData"
android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z"
android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z"
android:valueType="pathType"/>
</set>
|
Methods Summary |
---|
public void | applyTheme(android.content.res.Resources.Theme t)
super.applyTheme(t);
final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable;
if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
vectorDrawable.applyTheme(t);
}
|
public boolean | canApplyTheme()
return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme())
|| super.canApplyTheme();
|
public boolean | canReverse()
final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
final int size = animators.size();
for (int i = 0; i < size; i++) {
final Animator animator = animators.get(i);
if (!animator.canReverse()) {
return false;
}
}
return true;
|
public void | clearMutated()
super.clearMutated();
mAnimatedVectorState.mVectorDrawable.clearMutated();
mMutated = false;
|
public void | draw(android.graphics.Canvas canvas)
mAnimatedVectorState.mVectorDrawable.draw(canvas);
if (isStarted()) {
invalidateSelf();
}
|
public int | getAlpha()
return mAnimatedVectorState.mVectorDrawable.getAlpha();
|
public int | getChangingConfigurations()
return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations;
|
public ConstantState | getConstantState()
mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations();
return mAnimatedVectorState;
|
public int | getIntrinsicHeight()
return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
|
public int | getIntrinsicWidth()
return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
|
public int | getOpacity()
return mAnimatedVectorState.mVectorDrawable.getOpacity();
|
public void | getOutline(android.graphics.Outline outline)
mAnimatedVectorState.mVectorDrawable.getOutline(outline);
|
public void | inflate(android.content.res.Resources res, org.xmlpull.v1.XmlPullParser parser, android.util.AttributeSet attrs, android.content.res.Resources.Theme theme)
int eventType = parser.getEventType();
float pathErrorScale = 1;
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
final String tagName = parser.getName();
if (ANIMATED_VECTOR.equals(tagName)) {
final TypedArray a = obtainAttributes(res, theme, attrs,
R.styleable.AnimatedVectorDrawable);
int drawableRes = a.getResourceId(
R.styleable.AnimatedVectorDrawable_drawable, 0);
if (drawableRes != 0) {
VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable(
drawableRes, theme).mutate();
vectorDrawable.setAllowCaching(false);
vectorDrawable.setCallback(mCallback);
pathErrorScale = vectorDrawable.getPixelSize();
if (mAnimatedVectorState.mVectorDrawable != null) {
mAnimatedVectorState.mVectorDrawable.setCallback(null);
}
mAnimatedVectorState.mVectorDrawable = vectorDrawable;
}
a.recycle();
} else if (TARGET.equals(tagName)) {
final TypedArray a = obtainAttributes(res, theme, attrs,
R.styleable.AnimatedVectorDrawableTarget);
final String target = a.getString(
R.styleable.AnimatedVectorDrawableTarget_name);
int id = a.getResourceId(
R.styleable.AnimatedVectorDrawableTarget_animation, 0);
if (id != 0) {
Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id,
pathErrorScale);
setupAnimatorsForTarget(target, objectAnimator);
}
a.recycle();
}
}
eventType = parser.next();
}
|
public boolean | isRunning()
final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
final int size = animators.size();
for (int i = 0; i < size; i++) {
final Animator animator = animators.get(i);
if (animator.isRunning()) {
return true;
}
}
return false;
|
private boolean | isStarted()
final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
final int size = animators.size();
for (int i = 0; i < size; i++) {
final Animator animator = animators.get(i);
if (animator.isStarted()) {
return true;
}
}
return false;
|
public boolean | isStateful()
return mAnimatedVectorState.mVectorDrawable.isStateful();
|
public Drawable | mutate()
if (!mMutated && super.mutate() == this) {
mAnimatedVectorState = new AnimatedVectorDrawableState(
mAnimatedVectorState, mCallback, null);
mMutated = true;
}
return this;
|
protected void | onBoundsChange(android.graphics.Rect bounds)
mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
|
protected boolean | onLevelChange(int level)
return mAnimatedVectorState.mVectorDrawable.setLevel(level);
|
protected boolean | onStateChange(int[] state)
return mAnimatedVectorState.mVectorDrawable.setState(state);
|
public void | reverse()Reverses ongoing animations or starts pending animations in reverse.
NOTE: Only works if all animations support reverse. Otherwise, this will
do nothing.
// Only reverse when all the animators can be reverse. Otherwise, partially
// reverse is confusing.
if (!canReverse()) {
Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
return;
}
final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
final int size = animators.size();
for (int i = 0; i < size; i++) {
final Animator animator = animators.get(i);
animator.reverse();
}
|
public void | setAlpha(int alpha)
mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
|
public void | setColorFilter(android.graphics.ColorFilter colorFilter)
mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
|
public void | setHotspot(float x, float y)
mAnimatedVectorState.mVectorDrawable.setHotspot(x, y);
|
public void | setHotspotBounds(int left, int top, int right, int bottom)
mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom);
|
public void | setLayoutDirection(int layoutDirection){@hide}
mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection);
|
public void | setTintList(android.content.res.ColorStateList tint)
mAnimatedVectorState.mVectorDrawable.setTintList(tint);
|
public void | setTintMode(PorterDuff.Mode tintMode)
mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
|
public boolean | setVisible(boolean visible, boolean restart)
mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
return super.setVisible(visible, restart);
|
private void | setupAnimatorsForTarget(java.lang.String name, android.animation.Animator animator)
Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
animator.setTarget(target);
if (mAnimatedVectorState.mAnimators == null) {
mAnimatedVectorState.mAnimators = new ArrayList<Animator>();
mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>();
}
mAnimatedVectorState.mAnimators.add(animator);
mAnimatedVectorState.mTargetNameMap.put(animator, name);
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.v(LOGTAG, "add animator for target " + name + " " + animator);
}
|
public void | start()
// If any one of the animator has not ended, do nothing.
if (isStarted()) {
return;
}
// Otherwise, kick off every animator.
final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
final int size = animators.size();
for (int i = 0; i < size; i++) {
final Animator animator = animators.get(i);
animator.start();
}
invalidateSelf();
|
public void | stop()
final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
final int size = animators.size();
for (int i = 0; i < size; i++) {
final Animator animator = animators.get(i);
animator.end();
}
|