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