AnimatedVectorDrawable.java revision 7bc6a3f023ca3e1dde91fc97b6036dee3ba538a2
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
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
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
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
12 * the License.
13 */
14
15package android.graphics.drawable;
16
17import android.animation.Animator;
18import android.animation.AnimatorInflater;
19import android.animation.ValueAnimator;
20import android.content.res.Resources;
21import android.content.res.Resources.Theme;
22import android.content.res.TypedArray;
23import android.graphics.Canvas;
24import android.graphics.ColorFilter;
25import android.graphics.Rect;
26import android.util.AttributeSet;
27import android.util.Log;
28
29import com.android.internal.R;
30
31import org.xmlpull.v1.XmlPullParser;
32import org.xmlpull.v1.XmlPullParserException;
33
34import java.io.IOException;
35import java.util.ArrayList;
36
37/**
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.
41 * <p>
42 * AnimatedVectorDrawable are normally defined as 3 separate XML files.
43 * </p>
44 * <p>
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.
49 * </p>
50 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
51 * <pre>
52 * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android"; &gt;
53 *     &lt;size
54 *         android:height=&quot;64dp&quot;
55 *         android:width=&quot;64dp&quot; /&gt;
56 *     &lt;viewport
57 *         android:viewportHeight=&quot;600&quot;
58 *         android:viewportWidth=&quot;600&quot; /&gt;
59 *     &lt;group
60 *         android:name=&quot;rotationGroup&quot;
61 *         android:pivotX=&quot;300.0&quot;
62 *         android:pivotY=&quot;300.0&quot;
63 *         android:rotation=&quot;45.0&quot; &gt;
64 *         &lt;path
65 *             android:name=&quot;v&quot;
66 *             android:fill=&quot;#000000&quot;
67 *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
68 *     &lt;/group&gt;
69 * &lt;/vector&gt;
70 * </pre></li>
71 * <p>
72 * Second is the AnimatedVectorDrawable's xml file, which defines the target
73 * VectorDrawable, the target paths and groups to animate, the properties of the
74 * path and group to animate and the animations defined as the ObjectAnimators
75 * or AnimatorSets.
76 * </p>
77 * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
78 * Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
79 * <pre>
80 * &lt;animated-vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
81 *   android:drawable=&quot;@drawable/vectordrawable&quot; &gt;
82 *     &lt;target
83 *         android:name=&quot;rotationGroup&quot;
84 *         android:animation=&quot;@anim/rotation&quot; /&gt;
85 *     &lt;target
86 *         android:name=&quot;v&quot;
87 *         android:animation=&quot;@anim/path_morph&quot; /&gt;
88 * &lt;/animated-vector&gt;
89 * </pre></li>
90 * <p>
91 * Last is the Animator xml file, which is the same as a normal ObjectAnimator
92 * or AnimatorSet.
93 * To complete this example, here are the 2 animator files used in avd.xml:
94 * rotation.xml and path_morph.xml.
95 * </p>
96 * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees.
97 * <pre>
98 * &lt;objectAnimator
99 *     android:duration=&quot;6000&quot;
100 *     android:propertyName=&quot;rotation&quot;
101 *     android:valueFrom=&quot;0&quot;
102 *     android:valueTo=&quot;360&quot; /&gt;
103 * </pre></li>
104 * <li>Here is the path_morph.xml, which will morph the path from one shape to
105 * the other. Note that the paths must be compatible for morphing.
106 * In more details, the paths should have exact same length of commands , and
107 * exact same length of parameters for each commands.
108 * Note that the path string are better stored in strings.xml for reusing.
109 * <pre>
110 * &lt;set xmlns:android=&quot;http://schemas.android.com/apk/res/android">;
111 *     &lt;objectAnimator
112 *         android:duration=&quot;3000&quot;
113 *         android:propertyName=&quot;pathData&quot;
114 *         android:valueFrom=&quot;M300,70 l 0,-70 70,70 0,0   -70,70z&quot;
115 *         android:valueTo=&quot;M300,70 l 0,-70 70,0  0,140 -70,0 z&quot;
116 *         android:valueType=&quot;pathType&quot;/&gt;
117 * &lt;/set&gt;
118 * </pre></li>
119 *
120 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable
121 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name
122 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
123 */
124public class AnimatedVectorDrawable extends Drawable implements Animatable {
125    private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName();
126
127    private static final String ANIMATED_VECTOR = "animated-vector";
128    private static final String TARGET = "target";
129
130    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
131
132    private final AnimatedVectorDrawableState mAnimatedVectorState;
133
134
135    public AnimatedVectorDrawable() {
136        mAnimatedVectorState = new AnimatedVectorDrawableState(
137                new AnimatedVectorDrawableState(null));
138    }
139
140    private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res,
141            Theme theme) {
142        // TODO: Correctly handle the constant state for AVD.
143        mAnimatedVectorState = new AnimatedVectorDrawableState(state);
144        if (theme != null && canApplyTheme()) {
145            applyTheme(theme);
146        }
147    }
148
149    @Override
150    public ConstantState getConstantState() {
151        return null;
152    }
153
154    @Override
155    public void draw(Canvas canvas) {
156        mAnimatedVectorState.mVectorDrawable.draw(canvas);
157        if (isRunning()) {
158            invalidateSelf();
159        }
160    }
161
162    @Override
163    protected void onBoundsChange(Rect bounds) {
164        mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
165    }
166
167    @Override
168    public int getAlpha() {
169        return mAnimatedVectorState.mVectorDrawable.getAlpha();
170    }
171
172    @Override
173    public void setAlpha(int alpha) {
174        mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
175    }
176
177    @Override
178    public void setColorFilter(ColorFilter colorFilter) {
179        mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
180    }
181
182    @Override
183    public int getOpacity() {
184        return mAnimatedVectorState.mVectorDrawable.getOpacity();
185    }
186
187    @Override
188    public int getIntrinsicWidth() {
189        return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
190    }
191
192    @Override
193    public int getIntrinsicHeight() {
194        return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
195    }
196
197    @Override
198    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
199            throws XmlPullParserException, IOException {
200
201        int eventType = parser.getEventType();
202        while (eventType != XmlPullParser.END_DOCUMENT) {
203            if (eventType == XmlPullParser.START_TAG) {
204                final String tagName = parser.getName();
205                if (ANIMATED_VECTOR.equals(tagName)) {
206                    final TypedArray a = obtainAttributes(res, theme, attrs,
207                            R.styleable.AnimatedVectorDrawable);
208                    int drawableRes = a.getResourceId(
209                            R.styleable.AnimatedVectorDrawable_drawable, 0);
210                    if (drawableRes != 0) {
211                        mAnimatedVectorState.mVectorDrawable = (VectorDrawable) res.getDrawable(
212                                drawableRes, theme).mutate();
213                        mAnimatedVectorState.mVectorDrawable.setAllowCaching(false);
214                    }
215                    a.recycle();
216                } else if (TARGET.equals(tagName)) {
217                    final TypedArray a = obtainAttributes(res, theme, attrs,
218                            R.styleable.AnimatedVectorDrawableTarget);
219                    final String target = a.getString(
220                            R.styleable.AnimatedVectorDrawableTarget_name);
221
222                    int id = a.getResourceId(
223                            R.styleable.AnimatedVectorDrawableTarget_animation, 0);
224                    if (id != 0) {
225                        Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id);
226                        setupAnimatorsForTarget(target, objectAnimator);
227                    }
228                    a.recycle();
229                }
230            }
231
232            eventType = parser.next();
233        }
234    }
235
236    @Override
237    public boolean canApplyTheme() {
238        return super.canApplyTheme() || mAnimatedVectorState != null
239                && mAnimatedVectorState.mVectorDrawable != null
240                && mAnimatedVectorState.mVectorDrawable.canApplyTheme();
241    }
242
243    @Override
244    public void applyTheme(Theme t) {
245        super.applyTheme(t);
246
247        final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable;
248        if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
249            vectorDrawable.applyTheme(t);
250        }
251    }
252
253    private static class AnimatedVectorDrawableState extends ConstantState {
254        int mChangingConfigurations;
255        VectorDrawable mVectorDrawable;
256        ArrayList<Animator> mAnimators;
257
258        public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) {
259            if (copy != null) {
260                mChangingConfigurations = copy.mChangingConfigurations;
261                // TODO: Make sure the constant state are handled correctly.
262                mVectorDrawable = new VectorDrawable();
263                mVectorDrawable.setAllowCaching(false);
264                mAnimators = new ArrayList<Animator>();
265            }
266        }
267
268        @Override
269        public Drawable newDrawable() {
270            return new AnimatedVectorDrawable(this, null, null);
271        }
272
273        @Override
274        public Drawable newDrawable(Resources res) {
275            return new AnimatedVectorDrawable(this, res, null);
276        }
277
278        @Override
279        public Drawable newDrawable(Resources res, Theme theme) {
280            return new AnimatedVectorDrawable(this, res, theme);
281        }
282
283        @Override
284        public int getChangingConfigurations() {
285            return mChangingConfigurations;
286        }
287    }
288
289    private void setupAnimatorsForTarget(String name, Animator animator) {
290        Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
291        animator.setTarget(target);
292        mAnimatedVectorState.mAnimators.add(animator);
293        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
294            Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
295        }
296    }
297
298    @Override
299    public boolean isRunning() {
300        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
301        final int size = animators.size();
302        for (int i = 0; i < size; i++) {
303            final Animator animator = animators.get(i);
304            if (animator.isRunning()) {
305                return true;
306            }
307        }
308        return false;
309    }
310
311    @Override
312    public void start() {
313        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
314        final int size = animators.size();
315        for (int i = 0; i < size; i++) {
316            final Animator animator = animators.get(i);
317            if (animator.isPaused()) {
318                animator.resume();
319            } else if (!animator.isRunning()) {
320                animator.start();
321            }
322        }
323        invalidateSelf();
324    }
325
326    @Override
327    public void stop() {
328        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
329        final int size = animators.size();
330        for (int i = 0; i < size; i++) {
331            final Animator animator = animators.get(i);
332            animator.pause();
333        }
334    }
335
336    /**
337     * Reverses ongoing animations or starts pending animations in reverse.
338     * <p>
339     * NOTE: Only works of all animations are ValueAnimators.
340     * @hide
341     */
342    public void reverse() {
343        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
344        final int size = animators.size();
345        for (int i = 0; i < size; i++) {
346            final Animator animator = animators.get(i);
347            if (animator.canReverse()) {
348                animator.reverse();
349            } else {
350                Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
351            }
352        }
353    }
354
355    /**
356     * @hide
357     */
358    public boolean canReverse() {
359        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
360        final int size = animators.size();
361        for (int i = 0; i < size; i++) {
362            final Animator animator = animators.get(i);
363            if (!animator.canReverse()) {
364                return false;
365            }
366        }
367        return true;
368    }
369}
370