2 * Copyright (C) 2014 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
15 package android.graphics.drawable;
17 import android.animation.Animator;
18 import android.animation.AnimatorInflater;
19 import android.animation.ValueAnimator;
20 import android.content.res.Resources;
21 import android.content.res.Resources.Theme;
22 import android.content.res.TypedArray;
23 import android.graphics.Canvas;
24 import android.graphics.ColorFilter;
25 import android.graphics.Rect;
26 import android.util.AttributeSet;
27 import android.util.Log;
29 import com.android.internal.R;
31 import org.xmlpull.v1.XmlPullParser;
32 import org.xmlpull.v1.XmlPullParserException;
34 import java.io.IOException;
35 import java.util.ArrayList;
38 * This class uses {@link android.animation.ObjectAnimator} and
39 * {@link android.animation.AnimatorSet} to animate the properties of a
40 * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable.
42 * AnimatedVectorDrawable are normally defined as 3 separate XML files.
45 * First is the XML file for {@link android.graphics.drawable.VectorDrawable}.
46 * Note that we allow the animation happen on the group's attributes and path's
47 * attributes, which requires they are uniquely named in this xml file. Groups
48 * and paths without animations do not need names.
50 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
52 * <vector xmlns:android="http://schemas.android.com/apk/res/android"
53 * android:height="64dp"
54 * android:width="64dp"
55 * android:viewportHeight="600"
56 * android:viewportWidth="600" >
58 * android:name="rotationGroup"
59 * android:pivotX="300.0"
60 * android:pivotY="300.0"
61 * android:rotation="45.0" >
63 * android:name="v"
64 * android:fillColor="#000000"
65 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
70 * Second is the AnimatedVectorDrawable's xml file, which defines the target
71 * VectorDrawable, the target paths and groups to animate, the properties of the
72 * path and group to animate and the animations defined as the ObjectAnimators
75 * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
76 * Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
78 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
79 * android:drawable="@drawable/vectordrawable" >
81 * android:name="rotationGroup"
82 * android:animation="@anim/rotation" />
84 * android:name="v"
85 * android:animation="@anim/path_morph" />
86 * </animated-vector>
89 * Last is the Animator xml file, which is the same as a normal ObjectAnimator
91 * To complete this example, here are the 2 animator files used in avd.xml:
92 * rotation.xml and path_morph.xml.
94 * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees.
97 * android:duration="6000"
98 * android:propertyName="rotation"
99 * android:valueFrom="0"
100 * android:valueTo="360" />
102 * <li>Here is the path_morph.xml, which will morph the path from one shape to
103 * the other. Note that the paths must be compatible for morphing.
104 * In more details, the paths should have exact same length of commands , and
105 * exact same length of parameters for each commands.
106 * Note that the path string are better stored in strings.xml for reusing.
108 * <set xmlns:android="http://schemas.android.com/apk/res/android">
110 * android:duration="3000"
111 * android:propertyName="pathData"
112 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z"
113 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z"
114 * android:valueType="pathType"/>
118 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable
119 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name
120 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
122 public class AnimatedVectorDrawable extends Drawable implements Animatable {
123 private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName();
125 private static final String ANIMATED_VECTOR = "animated-vector";
126 private static final String TARGET = "target";
128 private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
130 private final AnimatedVectorDrawableState mAnimatedVectorState;
133 public AnimatedVectorDrawable() {
134 mAnimatedVectorState = new AnimatedVectorDrawableState(
135 new AnimatedVectorDrawableState(null));
138 private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res,
140 // TODO: Correctly handle the constant state for AVD.
141 mAnimatedVectorState = new AnimatedVectorDrawableState(state);
142 if (theme != null && canApplyTheme()) {
148 public ConstantState getConstantState() {
153 public void draw(Canvas canvas) {
154 mAnimatedVectorState.mVectorDrawable.draw(canvas);
161 protected void onBoundsChange(Rect bounds) {
162 mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
166 public int getAlpha() {
167 return mAnimatedVectorState.mVectorDrawable.getAlpha();
171 public void setAlpha(int alpha) {
172 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
176 public void setColorFilter(ColorFilter colorFilter) {
177 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
181 public int getOpacity() {
182 return mAnimatedVectorState.mVectorDrawable.getOpacity();
186 public int getIntrinsicWidth() {
187 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
191 public int getIntrinsicHeight() {
192 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
196 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
197 throws XmlPullParserException, IOException {
199 int eventType = parser.getEventType();
200 while (eventType != XmlPullParser.END_DOCUMENT) {
201 if (eventType == XmlPullParser.START_TAG) {
202 final String tagName = parser.getName();
203 if (ANIMATED_VECTOR.equals(tagName)) {
204 final TypedArray a = obtainAttributes(res, theme, attrs,
205 R.styleable.AnimatedVectorDrawable);
206 int drawableRes = a.getResourceId(
207 R.styleable.AnimatedVectorDrawable_drawable, 0);
208 if (drawableRes != 0) {
209 mAnimatedVectorState.mVectorDrawable = (VectorDrawable) res.getDrawable(
210 drawableRes, theme).mutate();
211 mAnimatedVectorState.mVectorDrawable.setAllowCaching(false);
214 } else if (TARGET.equals(tagName)) {
215 final TypedArray a = obtainAttributes(res, theme, attrs,
216 R.styleable.AnimatedVectorDrawableTarget);
217 final String target = a.getString(
218 R.styleable.AnimatedVectorDrawableTarget_name);
220 int id = a.getResourceId(
221 R.styleable.AnimatedVectorDrawableTarget_animation, 0);
223 Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id);
224 setupAnimatorsForTarget(target, objectAnimator);
230 eventType = parser.next();
235 public boolean canApplyTheme() {
236 return super.canApplyTheme() || mAnimatedVectorState != null
237 && mAnimatedVectorState.mVectorDrawable != null
238 && mAnimatedVectorState.mVectorDrawable.canApplyTheme();
242 public void applyTheme(Theme t) {
245 final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable;
246 if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
247 vectorDrawable.applyTheme(t);
251 private static class AnimatedVectorDrawableState extends ConstantState {
252 int mChangingConfigurations;
253 VectorDrawable mVectorDrawable;
254 ArrayList<Animator> mAnimators;
256 public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) {
258 mChangingConfigurations = copy.mChangingConfigurations;
259 // TODO: Make sure the constant state are handled correctly.
260 mVectorDrawable = new VectorDrawable();
261 mVectorDrawable.setAllowCaching(false);
262 mAnimators = new ArrayList<Animator>();
267 public Drawable newDrawable() {
268 return new AnimatedVectorDrawable(this, null, null);
272 public Drawable newDrawable(Resources res) {
273 return new AnimatedVectorDrawable(this, res, null);
277 public Drawable newDrawable(Resources res, Theme theme) {
278 return new AnimatedVectorDrawable(this, res, theme);
282 public int getChangingConfigurations() {
283 return mChangingConfigurations;
287 private void setupAnimatorsForTarget(String name, Animator animator) {
288 Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
289 animator.setTarget(target);
290 mAnimatedVectorState.mAnimators.add(animator);
291 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
292 Log.v(LOGTAG, "add animator for target " + name + " " + animator);
297 public boolean isRunning() {
298 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
299 final int size = animators.size();
300 for (int i = 0; i < size; i++) {
301 final Animator animator = animators.get(i);
302 if (animator.isRunning()) {
309 private boolean isStarted() {
310 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
311 final int size = animators.size();
312 for (int i = 0; i < size; i++) {
313 final Animator animator = animators.get(i);
314 if (animator.isStarted()) {
322 public void start() {
323 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
324 final int size = animators.size();
325 for (int i = 0; i < size; i++) {
326 final Animator animator = animators.get(i);
327 if (!animator.isStarted()) {
336 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
337 final int size = animators.size();
338 for (int i = 0; i < size; i++) {
339 final Animator animator = animators.get(i);
345 * Reverses ongoing animations or starts pending animations in reverse.
347 * NOTE: Only works of all animations are ValueAnimators.
350 public void reverse() {
351 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
352 final int size = animators.size();
353 for (int i = 0; i < size; i++) {
354 final Animator animator = animators.get(i);
355 if (animator.canReverse()) {
358 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
366 public boolean canReverse() {
367 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
368 final int size = animators.size();
369 for (int i = 0; i < size; i++) {
370 final Animator animator = animators.get(i);
371 if (!animator.canReverse()) {