AnimatedVectorDrawable.java revision fd3c4744f265c5277e6e2641a18d5ec3dff19f6b
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.annotation.NonNull;
21import android.content.res.ColorStateList;
22import android.content.res.Resources;
23import android.content.res.Resources.Theme;
24import android.content.res.TypedArray;
25import android.graphics.Canvas;
26import android.graphics.ColorFilter;
27import android.graphics.Outline;
28import android.graphics.PorterDuff;
29import android.graphics.Rect;
30import android.util.AttributeSet;
31import android.util.Log;
32
33import com.android.internal.R;
34
35import org.xmlpull.v1.XmlPullParser;
36import org.xmlpull.v1.XmlPullParserException;
37
38import java.io.IOException;
39import java.util.ArrayList;
40
41/**
42 * This class uses {@link android.animation.ObjectAnimator} and
43 * {@link android.animation.AnimatorSet} to animate the properties of a
44 * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable.
45 * <p>
46 * AnimatedVectorDrawable are normally defined as 3 separate XML files.
47 * </p>
48 * <p>
49 * First is the XML file for {@link android.graphics.drawable.VectorDrawable}.
50 * Note that we allow the animation happen on the group's attributes and path's
51 * attributes, which requires they are uniquely named in this xml file. Groups
52 * and paths without animations do not need names.
53 * </p>
54 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
55 * <pre>
56 * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
57 *     android:height=&quot;64dp&quot;
58 *     android:width=&quot;64dp&quot;
59 *     android:viewportHeight=&quot;600&quot;
60 *     android:viewportWidth=&quot;600&quot; &gt;
61 *     &lt;group
62 *         android:name=&quot;rotationGroup&quot;
63 *         android:pivotX=&quot;300.0&quot;
64 *         android:pivotY=&quot;300.0&quot;
65 *         android:rotation=&quot;45.0&quot; &gt;
66 *         &lt;path
67 *             android:name=&quot;v&quot;
68 *             android:fillColor=&quot;#000000&quot;
69 *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
70 *     &lt;/group&gt;
71 * &lt;/vector&gt;
72 * </pre></li>
73 * <p>
74 * Second is the AnimatedVectorDrawable's xml file, which defines the target
75 * VectorDrawable, the target paths and groups to animate, the properties of the
76 * path and group to animate and the animations defined as the ObjectAnimators
77 * or AnimatorSets.
78 * </p>
79 * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
80 * Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
81 * <pre>
82 * &lt;animated-vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
83 *   android:drawable=&quot;@drawable/vectordrawable&quot; &gt;
84 *     &lt;target
85 *         android:name=&quot;rotationGroup&quot;
86 *         android:animation=&quot;@anim/rotation&quot; /&gt;
87 *     &lt;target
88 *         android:name=&quot;v&quot;
89 *         android:animation=&quot;@anim/path_morph&quot; /&gt;
90 * &lt;/animated-vector&gt;
91 * </pre></li>
92 * <p>
93 * Last is the Animator xml file, which is the same as a normal ObjectAnimator
94 * or AnimatorSet.
95 * To complete this example, here are the 2 animator files used in avd.xml:
96 * rotation.xml and path_morph.xml.
97 * </p>
98 * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees.
99 * <pre>
100 * &lt;objectAnimator
101 *     android:duration=&quot;6000&quot;
102 *     android:propertyName=&quot;rotation&quot;
103 *     android:valueFrom=&quot;0&quot;
104 *     android:valueTo=&quot;360&quot; /&gt;
105 * </pre></li>
106 * <li>Here is the path_morph.xml, which will morph the path from one shape to
107 * the other. Note that the paths must be compatible for morphing.
108 * In more details, the paths should have exact same length of commands , and
109 * exact same length of parameters for each commands.
110 * Note that the path string are better stored in strings.xml for reusing.
111 * <pre>
112 * &lt;set xmlns:android=&quot;http://schemas.android.com/apk/res/android">;
113 *     &lt;objectAnimator
114 *         android:duration=&quot;3000&quot;
115 *         android:propertyName=&quot;pathData&quot;
116 *         android:valueFrom=&quot;M300,70 l 0,-70 70,70 0,0   -70,70z&quot;
117 *         android:valueTo=&quot;M300,70 l 0,-70 70,0  0,140 -70,0 z&quot;
118 *         android:valueType=&quot;pathType&quot;/&gt;
119 * &lt;/set&gt;
120 * </pre></li>
121 *
122 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable
123 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name
124 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
125 */
126public class AnimatedVectorDrawable extends Drawable implements Animatable {
127    private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName();
128
129    private static final String ANIMATED_VECTOR = "animated-vector";
130    private static final String TARGET = "target";
131
132    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
133
134    private final AnimatedVectorDrawableState mAnimatedVectorState;
135
136    public AnimatedVectorDrawable() {
137        mAnimatedVectorState = new AnimatedVectorDrawableState(
138                new AnimatedVectorDrawableState(null));
139    }
140
141    private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res,
142            Theme theme) {
143        // TODO: Correctly handle the constant state for AVD.
144        mAnimatedVectorState = new AnimatedVectorDrawableState(state);
145        if (theme != null && canApplyTheme()) {
146            applyTheme(theme);
147        }
148    }
149
150    @Override
151    public ConstantState getConstantState() {
152        return null;
153    }
154
155    @Override
156    public void draw(Canvas canvas) {
157        mAnimatedVectorState.mVectorDrawable.draw(canvas);
158        if (isStarted()) {
159            invalidateSelf();
160        }
161    }
162
163    @Override
164    protected void onBoundsChange(Rect bounds) {
165        mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
166    }
167
168    @Override
169    protected boolean onStateChange(int[] state) {
170        return mAnimatedVectorState.mVectorDrawable.setState(state);
171    }
172
173    @Override
174    protected boolean onLevelChange(int level) {
175        return mAnimatedVectorState.mVectorDrawable.setLevel(level);
176    }
177
178    @Override
179    public int getAlpha() {
180        return mAnimatedVectorState.mVectorDrawable.getAlpha();
181    }
182
183    @Override
184    public void setAlpha(int alpha) {
185        mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
186    }
187
188    @Override
189    public void setColorFilter(ColorFilter colorFilter) {
190        mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
191    }
192
193    @Override
194    public void setTintList(ColorStateList tint) {
195        mAnimatedVectorState.mVectorDrawable.setTintList(tint);
196    }
197
198    @Override
199    public void setHotspot(float x, float y) {
200        mAnimatedVectorState.mVectorDrawable.setHotspot(x, y);
201    }
202
203    @Override
204    public void setHotspotBounds(int left, int top, int right, int bottom) {
205        mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom);
206    }
207
208    @Override
209    public void setTintMode(PorterDuff.Mode tintMode) {
210        mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
211    }
212
213    @Override
214    public boolean setVisible(boolean visible, boolean restart) {
215        mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
216        return super.setVisible(visible, restart);
217    }
218
219    /** {@hide} */
220    @Override
221    public void setLayoutDirection(int layoutDirection) {
222        mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection);
223    }
224
225    @Override
226    public boolean isStateful() {
227        return mAnimatedVectorState.mVectorDrawable.isStateful();
228    }
229
230    @Override
231    public int getOpacity() {
232        return mAnimatedVectorState.mVectorDrawable.getOpacity();
233    }
234
235    @Override
236    public int getIntrinsicWidth() {
237        return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
238    }
239
240    @Override
241    public int getIntrinsicHeight() {
242        return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
243    }
244
245    @Override
246    public void getOutline(@NonNull Outline outline) {
247        mAnimatedVectorState.mVectorDrawable.getOutline(outline);
248    }
249
250    @Override
251    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
252            throws XmlPullParserException, IOException {
253
254        int eventType = parser.getEventType();
255        float pathErrorScale = 1;
256        while (eventType != XmlPullParser.END_DOCUMENT) {
257            if (eventType == XmlPullParser.START_TAG) {
258                final String tagName = parser.getName();
259                if (ANIMATED_VECTOR.equals(tagName)) {
260                    final TypedArray a = obtainAttributes(res, theme, attrs,
261                            R.styleable.AnimatedVectorDrawable);
262                    int drawableRes = a.getResourceId(
263                            R.styleable.AnimatedVectorDrawable_drawable, 0);
264                    if (drawableRes != 0) {
265                        VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable(
266                                drawableRes, theme).mutate();
267                        vectorDrawable.setAllowCaching(false);
268                        pathErrorScale = vectorDrawable.getPixelSize();
269                        mAnimatedVectorState.mVectorDrawable = vectorDrawable;
270                    }
271                    a.recycle();
272                } else if (TARGET.equals(tagName)) {
273                    final TypedArray a = obtainAttributes(res, theme, attrs,
274                            R.styleable.AnimatedVectorDrawableTarget);
275                    final String target = a.getString(
276                            R.styleable.AnimatedVectorDrawableTarget_name);
277
278                    int id = a.getResourceId(
279                            R.styleable.AnimatedVectorDrawableTarget_animation, 0);
280                    if (id != 0) {
281                        Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id,
282                                pathErrorScale);
283                        setupAnimatorsForTarget(target, objectAnimator);
284                    }
285                    a.recycle();
286                }
287            }
288
289            eventType = parser.next();
290        }
291    }
292
293    @Override
294    public boolean canApplyTheme() {
295        return super.canApplyTheme() || mAnimatedVectorState != null
296                && mAnimatedVectorState.mVectorDrawable != null
297                && mAnimatedVectorState.mVectorDrawable.canApplyTheme();
298    }
299
300    @Override
301    public void applyTheme(Theme t) {
302        super.applyTheme(t);
303
304        final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable;
305        if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
306            vectorDrawable.applyTheme(t);
307        }
308    }
309
310    private static class AnimatedVectorDrawableState extends ConstantState {
311        int mChangingConfigurations;
312        VectorDrawable mVectorDrawable;
313        ArrayList<Animator> mAnimators;
314
315        public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) {
316            if (copy != null) {
317                mChangingConfigurations = copy.mChangingConfigurations;
318                // TODO: Make sure the constant state are handled correctly.
319                mVectorDrawable = new VectorDrawable();
320                mVectorDrawable.setAllowCaching(false);
321                mAnimators = new ArrayList<Animator>();
322            }
323        }
324
325        @Override
326        public Drawable newDrawable() {
327            return new AnimatedVectorDrawable(this, null, null);
328        }
329
330        @Override
331        public Drawable newDrawable(Resources res) {
332            return new AnimatedVectorDrawable(this, res, null);
333        }
334
335        @Override
336        public Drawable newDrawable(Resources res, Theme theme) {
337            return new AnimatedVectorDrawable(this, res, theme);
338        }
339
340        @Override
341        public int getChangingConfigurations() {
342            return mChangingConfigurations;
343        }
344    }
345
346    private void setupAnimatorsForTarget(String name, Animator animator) {
347        Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
348        animator.setTarget(target);
349        mAnimatedVectorState.mAnimators.add(animator);
350        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
351            Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
352        }
353    }
354
355    @Override
356    public boolean isRunning() {
357        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
358        final int size = animators.size();
359        for (int i = 0; i < size; i++) {
360            final Animator animator = animators.get(i);
361            if (animator.isRunning()) {
362                return true;
363            }
364        }
365        return false;
366    }
367
368    private boolean isStarted() {
369        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
370        final int size = animators.size();
371        for (int i = 0; i < size; i++) {
372            final Animator animator = animators.get(i);
373            if (animator.isStarted()) {
374                return true;
375            }
376        }
377        return false;
378    }
379
380    @Override
381    public void start() {
382        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
383        final int size = animators.size();
384        for (int i = 0; i < size; i++) {
385            final Animator animator = animators.get(i);
386            if (!animator.isStarted()) {
387                animator.start();
388            }
389        }
390        invalidateSelf();
391    }
392
393    @Override
394    public void stop() {
395        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
396        final int size = animators.size();
397        for (int i = 0; i < size; i++) {
398            final Animator animator = animators.get(i);
399            animator.end();
400        }
401    }
402
403    /**
404     * Reverses ongoing animations or starts pending animations in reverse.
405     * <p>
406     * NOTE: Only works of all animations are ValueAnimators.
407     * @hide
408     */
409    public void reverse() {
410        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
411        final int size = animators.size();
412        for (int i = 0; i < size; i++) {
413            final Animator animator = animators.get(i);
414            if (animator.canReverse()) {
415                animator.reverse();
416            } else {
417                Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
418            }
419        }
420    }
421
422    /**
423     * @hide
424     */
425    public boolean canReverse() {
426        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
427        final int size = animators.size();
428        for (int i = 0; i < size; i++) {
429            final Animator animator = animators.get(i);
430            if (!animator.canReverse()) {
431                return false;
432            }
433        }
434        return true;
435    }
436}
437