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