1/*
2 * Copyright (C) 2015 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.support.graphics.drawable;
16
17import android.animation.Animator;
18import android.animation.AnimatorInflater;
19import android.animation.AnimatorSet;
20import android.animation.ArgbEvaluator;
21import android.animation.ObjectAnimator;
22import android.annotation.TargetApi;
23import android.content.Context;
24import android.content.res.ColorStateList;
25import android.content.res.Resources;
26import android.content.res.Resources.Theme;
27import android.content.res.TypedArray;
28import android.graphics.Canvas;
29import android.graphics.ColorFilter;
30import android.graphics.PorterDuff;
31import android.graphics.Rect;
32import android.graphics.drawable.Animatable;
33import android.graphics.drawable.AnimatedVectorDrawable;
34import android.graphics.drawable.Drawable;
35import android.os.Build;
36import android.support.annotation.DrawableRes;
37import android.support.annotation.NonNull;
38import android.support.annotation.Nullable;
39import android.support.v4.content.res.ResourcesCompat;
40import android.support.v4.graphics.drawable.DrawableCompat;
41import android.support.v4.util.ArrayMap;
42import android.util.AttributeSet;
43import android.util.Log;
44import android.util.Xml;
45
46import org.xmlpull.v1.XmlPullParser;
47import org.xmlpull.v1.XmlPullParserException;
48
49import java.io.IOException;
50import java.util.ArrayList;
51import java.util.List;
52
53/**
54 * For API 24 and above, this class is delegating to the framework's {@link AnimatedVectorDrawable}.
55 * For older API version, this class uses {@link android.animation.ObjectAnimator} and
56 * {@link android.animation.AnimatorSet} to animate the properties of a
57 * {@link VectorDrawableCompat} to create an animated drawable.
58 * <p/>
59 * AnimatedVectorDrawableCompat are defined in the same XML format as {@link AnimatedVectorDrawable}.
60 * <p/>
61 * Here are all the animatable attributes in {@link VectorDrawableCompat}:
62 * <table border="2" align="center" cellpadding="5">
63 *     <thead>
64 *         <tr>
65 *             <th>Element Name</th>
66 *             <th>Animatable attribute name</th>
67 *         </tr>
68 *     </thead>
69 *     <tr>
70 *         <td>&lt;vector&gt;</td>
71 *         <td>alpha</td>
72 *     </tr>
73 *     <tr>
74 *         <td rowspan="7">&lt;group&gt;</td>
75 *         <td>rotation</td>
76 *     </tr>
77 *     <tr>
78 *         <td>pivotX</td>
79 *     </tr>
80 *     <tr>
81 *         <td>pivotY</td>
82 *     </tr>
83 *     <tr>
84 *         <td>scaleX</td>
85 *     </tr>
86 *     <tr>
87 *         <td>scaleY</td>
88 *     </tr>
89 *     <tr>
90 *         <td>translateX</td>
91 *     </tr>
92 *     <tr>
93 *         <td>translateY</td>
94 *     </tr>
95 *     <tr>
96 *         <td rowspan="8">&lt;path&gt;</td>
97 *         <td>fillColor</td>
98 *     </tr>
99 *     <tr>
100 *         <td>strokeColor</td>
101 *     </tr>
102 *     <tr>
103 *         <td>strokeWidth</td>
104 *     </tr>
105 *     <tr>
106 *         <td>strokeAlpha</td>
107 *     </tr>
108 *     <tr>
109 *         <td>fillAlpha</td>
110 *     </tr>
111 *     <tr>
112 *         <td>trimPathStart</td>
113 *     </tr>
114 *     <tr>
115 *         <td>trimPathOffset</td>
116 *     </tr>
117 * </table>
118 * <p/>
119 * You can always create a AnimatedVectorDrawableCompat object and use it as a Drawable by the Java
120 * API. In order to refer to AnimatedVectorDrawableCompat inside a XML file, you can use
121 * app:srcCompat attribute in AppCompat library's ImageButton or ImageView.
122 * <p/>
123 * Note that the animation in AnimatedVectorDrawableCompat has to be valid and functional based on
124 * the SDK version the app will be running on. Before SDK version 21, the animation system didn't
125 * support the following features:
126 * <ul>
127 * <li>Path Morphing (PathType evaluator). This is used for morphing one path into another.</li>
128 * <li>Path Interpolation. This is used to defined a flexible interpolator (represented as a path)
129 * instead of the system defined ones like LinearInterpolator.</li>
130 * <li>Animating 2 values in one ObjectAnimator according to one path's X value and Y value. One
131 * usage is moving one object in both X and Y dimensions along an path.</li>
132 * </ul>
133 */
134@TargetApi(Build.VERSION_CODES.LOLLIPOP)
135public class AnimatedVectorDrawableCompat extends VectorDrawableCommon implements Animatable {
136    private static final String LOGTAG = "AnimatedVDCompat";
137
138    private static final String ANIMATED_VECTOR = "animated-vector";
139    private static final String TARGET = "target";
140
141    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
142
143    private AnimatedVectorDrawableCompatState mAnimatedVectorState;
144
145    private Context mContext;
146
147    private ArgbEvaluator mArgbEvaluator = null;
148
149    AnimatedVectorDrawableDelegateState mCachedConstantStateDelegate;
150
151    AnimatedVectorDrawableCompat() {
152        this(null, null, null);
153    }
154
155    private AnimatedVectorDrawableCompat(@Nullable Context context) {
156        this(context, null, null);
157    }
158
159    private AnimatedVectorDrawableCompat(@Nullable Context context,
160                                         @Nullable AnimatedVectorDrawableCompatState state,
161                                         @Nullable Resources res) {
162        mContext = context;
163        if (state != null) {
164            mAnimatedVectorState = state;
165        } else {
166            mAnimatedVectorState = new AnimatedVectorDrawableCompatState(context, state, mCallback,
167                    res);
168        }
169    }
170
171    /**
172     * mutate() will be effective only if the getConstantState() is returning non-null.
173     * Otherwise, it just return the current object without modification.
174     */
175    @Override
176    public Drawable mutate() {
177        if (mDelegateDrawable != null) {
178            mDelegateDrawable.mutate();
179        }
180        // For older platforms that there is no delegated drawable, we just return this without
181        // any modification here, and the getConstantState() will return null in this case.
182        return this;
183    }
184
185
186    /**
187     * Create a AnimatedVectorDrawableCompat object.
188     *
189     * @param context the context for creating the animators.
190     * @param resId   the resource ID for AnimatedVectorDrawableCompat object.
191     * @return a new AnimatedVectorDrawableCompat or null if parsing error is found.
192     */
193    @Nullable
194    public static AnimatedVectorDrawableCompat create(@NonNull Context context,
195                                                      @DrawableRes int resId) {
196        if (Build.VERSION.SDK_INT >= 24) {
197            final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
198            drawable.mDelegateDrawable = ResourcesCompat.getDrawable(context.getResources(), resId,
199                    context.getTheme());
200            drawable.mDelegateDrawable.setCallback(drawable.mCallback);
201            drawable.mCachedConstantStateDelegate = new AnimatedVectorDrawableDelegateState(
202                    drawable.mDelegateDrawable.getConstantState());
203            return drawable;
204        }
205        Resources resources = context.getResources();
206        try {
207            final XmlPullParser parser = resources.getXml(resId);
208            final AttributeSet attrs = Xml.asAttributeSet(parser);
209            int type;
210            while ((type = parser.next()) != XmlPullParser.START_TAG
211                    && type != XmlPullParser.END_DOCUMENT) {
212                // Empty loop
213            }
214            if (type != XmlPullParser.START_TAG) {
215                throw new XmlPullParserException("No start tag found");
216            }
217            return createFromXmlInner(context, context.getResources(), parser, attrs,
218                    context.getTheme());
219        } catch (XmlPullParserException e) {
220            Log.e(LOGTAG, "parser error", e);
221        } catch (IOException e) {
222            Log.e(LOGTAG, "parser error", e);
223        }
224        return null;
225    }
226
227    /**
228     * Create a AnimatedVectorDrawableCompat from inside an XML document using an optional
229     * {@link Theme}. Called on a parser positioned at a tag in an XML
230     * document, tries to create a Drawable from that tag. Returns {@code null}
231     * if the tag is not a valid drawable.
232     */
233    public static AnimatedVectorDrawableCompat createFromXmlInner(Context context, Resources r,
234            XmlPullParser parser, AttributeSet attrs, Theme theme)
235            throws XmlPullParserException, IOException {
236        final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
237        drawable.inflate(r, parser, attrs, theme);
238        return drawable;
239    }
240
241    /**
242     * {@inheritDoc}
243     * <strong>Note</strong> that we don't support constant state when SDK < 24.
244     * Make sure you check the return value before using it.
245     */
246    @Override
247    public ConstantState getConstantState() {
248        if (mDelegateDrawable != null) {
249            return new AnimatedVectorDrawableDelegateState(mDelegateDrawable.getConstantState());
250        }
251        // We can't support constant state in older platform.
252        // We need Context to create the animator, and we can't save the context in the constant
253        // state.
254        return null;
255    }
256
257    @Override
258    public int getChangingConfigurations() {
259        if (mDelegateDrawable != null) {
260            return mDelegateDrawable.getChangingConfigurations();
261        }
262        return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations;
263    }
264
265    @Override
266    public void draw(Canvas canvas) {
267        if (mDelegateDrawable != null) {
268            mDelegateDrawable.draw(canvas);
269            return;
270        }
271        mAnimatedVectorState.mVectorDrawable.draw(canvas);
272        if (isStarted()) {
273            invalidateSelf();
274        }
275    }
276
277    @Override
278    protected void onBoundsChange(Rect bounds) {
279        if (mDelegateDrawable != null) {
280            mDelegateDrawable.setBounds(bounds);
281            return;
282        }
283        mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
284    }
285
286    @Override
287    protected boolean onStateChange(int[] state) {
288        if (mDelegateDrawable != null) {
289            return mDelegateDrawable.setState(state);
290        }
291        return mAnimatedVectorState.mVectorDrawable.setState(state);
292    }
293
294    @Override
295    protected boolean onLevelChange(int level) {
296        if (mDelegateDrawable != null) {
297            return mDelegateDrawable.setLevel(level);
298        }
299        return mAnimatedVectorState.mVectorDrawable.setLevel(level);
300    }
301
302    @Override
303    public int getAlpha() {
304        if (mDelegateDrawable != null) {
305            return DrawableCompat.getAlpha(mDelegateDrawable);
306        }
307        return mAnimatedVectorState.mVectorDrawable.getAlpha();
308    }
309
310    @Override
311    public void setAlpha(int alpha) {
312        if (mDelegateDrawable != null) {
313            mDelegateDrawable.setAlpha(alpha);
314            return;
315        }
316        mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
317    }
318
319    @Override
320    public void setColorFilter(ColorFilter colorFilter) {
321        if (mDelegateDrawable != null) {
322            mDelegateDrawable.setColorFilter(colorFilter);
323            return;
324        }
325        mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
326    }
327
328    @Override
329    public void setTint(int tint) {
330        if (mDelegateDrawable != null) {
331            DrawableCompat.setTint(mDelegateDrawable, tint);
332            return;
333        }
334
335        mAnimatedVectorState.mVectorDrawable.setTint(tint);
336    }
337
338    @Override
339    public void setTintList(ColorStateList tint) {
340        if (mDelegateDrawable != null) {
341            DrawableCompat.setTintList(mDelegateDrawable, tint);
342            return;
343        }
344
345        mAnimatedVectorState.mVectorDrawable.setTintList(tint);
346    }
347
348    @Override
349    public void setTintMode(PorterDuff.Mode tintMode) {
350        if (mDelegateDrawable != null) {
351            DrawableCompat.setTintMode(mDelegateDrawable, tintMode);
352            return;
353        }
354
355        mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
356    }
357
358    @Override
359    public boolean setVisible(boolean visible, boolean restart) {
360        if (mDelegateDrawable != null) {
361            return mDelegateDrawable.setVisible(visible, restart);
362        }
363        mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
364        return super.setVisible(visible, restart);
365    }
366
367    @Override
368    public boolean isStateful() {
369        if (mDelegateDrawable != null) {
370            return mDelegateDrawable.isStateful();
371        }
372        return mAnimatedVectorState.mVectorDrawable.isStateful();
373    }
374
375    @Override
376    public int getOpacity() {
377        if (mDelegateDrawable != null) {
378            return mDelegateDrawable.getOpacity();
379        }
380        return mAnimatedVectorState.mVectorDrawable.getOpacity();
381    }
382
383    @Override
384    public int getIntrinsicWidth() {
385        if (mDelegateDrawable != null) {
386            return mDelegateDrawable.getIntrinsicWidth();
387        }
388        return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
389    }
390
391    @Override
392    public int getIntrinsicHeight() {
393        if (mDelegateDrawable != null) {
394            return mDelegateDrawable.getIntrinsicHeight();
395        }
396        return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
397    }
398
399    @Override
400    public boolean isAutoMirrored() {
401        if (mDelegateDrawable != null) {
402            return DrawableCompat.isAutoMirrored(mDelegateDrawable);
403        }
404        return mAnimatedVectorState.mVectorDrawable.isAutoMirrored();
405    }
406
407    @Override
408    public void setAutoMirrored(boolean mirrored) {
409        if (mDelegateDrawable != null) {
410            mDelegateDrawable.setAutoMirrored(mirrored);
411            return;
412        }
413        mAnimatedVectorState.mVectorDrawable.setAutoMirrored(mirrored);
414    }
415
416    /**
417     * Obtains styled attributes from the theme, if available, or unstyled
418     * resources if the theme is null.
419     */
420    static TypedArray obtainAttributes(
421            Resources res, Theme theme, AttributeSet set, int[] attrs) {
422        if (theme == null) {
423            return res.obtainAttributes(set, attrs);
424        }
425        return theme.obtainStyledAttributes(set, attrs, 0, 0);
426    }
427
428    @Override
429    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
430            throws XmlPullParserException, IOException {
431        if (mDelegateDrawable != null) {
432            DrawableCompat.inflate(mDelegateDrawable, res, parser, attrs, theme);
433            return;
434        }
435        int eventType = parser.getEventType();
436        final int innerDepth = parser.getDepth() + 1;
437
438        // Parse everything until the end of the animated-vector element.
439        while (eventType != XmlPullParser.END_DOCUMENT
440                && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
441            if (eventType == XmlPullParser.START_TAG) {
442                final String tagName = parser.getName();
443                if (DBG_ANIMATION_VECTOR_DRAWABLE) {
444                    Log.v(LOGTAG, "tagName is " + tagName);
445                }
446                if (ANIMATED_VECTOR.equals(tagName)) {
447                    final TypedArray a =
448                            obtainAttributes(res, theme, attrs,
449                                    AndroidResources.styleable_AnimatedVectorDrawable);
450
451                    int drawableRes = a.getResourceId(
452                            AndroidResources.styleable_AnimatedVectorDrawable_drawable, 0);
453                    if (DBG_ANIMATION_VECTOR_DRAWABLE) {
454                        Log.v(LOGTAG, "drawableRes is " + drawableRes);
455                    }
456                    if (drawableRes != 0) {
457                        VectorDrawableCompat vectorDrawable = VectorDrawableCompat.create(res,
458                                drawableRes, theme);
459                        vectorDrawable.setAllowCaching(false);
460                        vectorDrawable.setCallback(mCallback);
461                        if (mAnimatedVectorState.mVectorDrawable != null) {
462                            mAnimatedVectorState.mVectorDrawable.setCallback(null);
463                        }
464                        mAnimatedVectorState.mVectorDrawable = vectorDrawable;
465                    }
466                    a.recycle();
467                } else if (TARGET.equals(tagName)) {
468                    final TypedArray a =
469                            res.obtainAttributes(attrs,
470                                    AndroidResources.styleable_AnimatedVectorDrawableTarget);
471                    final String target = a.getString(
472                            AndroidResources.styleable_AnimatedVectorDrawableTarget_name);
473
474                    int id = a.getResourceId(
475                            AndroidResources.styleable_AnimatedVectorDrawableTarget_animation, 0);
476                    if (id != 0) {
477                        if (mContext != null) {
478                            Animator objectAnimator = AnimatorInflater.loadAnimator(mContext, id);
479                            setupAnimatorsForTarget(target, objectAnimator);
480                        } else {
481                            throw new IllegalStateException("Context can't be null when inflating" +
482                                    " animators");
483                        }
484                    }
485                    a.recycle();
486                }
487            }
488
489            eventType = parser.next();
490        }
491    }
492
493    @Override
494    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs)
495            throws XmlPullParserException, IOException {
496        inflate(res, parser, attrs, null);
497    }
498
499    @Override
500    public void applyTheme(Theme t) {
501        if (mDelegateDrawable != null) {
502            DrawableCompat.applyTheme(mDelegateDrawable, t);
503            return;
504        }
505        // TODO: support theming in older platform.
506        return;
507    }
508
509    @Override
510    public boolean canApplyTheme() {
511        if (mDelegateDrawable != null) {
512            return DrawableCompat.canApplyTheme(mDelegateDrawable);
513        }
514        // TODO: support theming in older platform.
515        return false;
516    }
517
518    /**
519     * Constant state for delegating the creating drawable job.
520     * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains
521     * a delegated VectorDrawable instance.
522     */
523    private static class AnimatedVectorDrawableDelegateState extends ConstantState {
524        private final ConstantState mDelegateState;
525
526        public AnimatedVectorDrawableDelegateState(ConstantState state) {
527            mDelegateState = state;
528        }
529
530        @Override
531        public Drawable newDrawable() {
532            AnimatedVectorDrawableCompat drawableCompat =
533                    new AnimatedVectorDrawableCompat();
534            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable();
535            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
536            return drawableCompat;
537        }
538
539        @Override
540        public Drawable newDrawable(Resources res) {
541            AnimatedVectorDrawableCompat drawableCompat =
542                    new AnimatedVectorDrawableCompat();
543            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res);
544            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
545            return drawableCompat;
546        }
547
548        @Override
549        public Drawable newDrawable(Resources res, Theme theme) {
550            AnimatedVectorDrawableCompat drawableCompat =
551                    new AnimatedVectorDrawableCompat();
552            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res, theme);
553            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
554            return drawableCompat;
555        }
556
557        @Override
558        public boolean canApplyTheme() {
559            return mDelegateState.canApplyTheme();
560        }
561
562        @Override
563        public int getChangingConfigurations() {
564            return mDelegateState.getChangingConfigurations();
565        }
566    }
567
568    private static class AnimatedVectorDrawableCompatState extends ConstantState {
569        int mChangingConfigurations;
570        VectorDrawableCompat mVectorDrawable;
571        ArrayList<Animator> mAnimators;
572        ArrayMap<Animator, String> mTargetNameMap;
573
574        public AnimatedVectorDrawableCompatState(Context context,
575                AnimatedVectorDrawableCompatState copy, Callback owner, Resources res) {
576            if (copy != null) {
577                mChangingConfigurations = copy.mChangingConfigurations;
578                if (copy.mVectorDrawable != null) {
579                    final ConstantState cs = copy.mVectorDrawable.getConstantState();
580                    if (res != null) {
581                        mVectorDrawable = (VectorDrawableCompat) cs.newDrawable(res);
582                    } else {
583                        mVectorDrawable = (VectorDrawableCompat) cs.newDrawable();
584                    }
585                    mVectorDrawable = (VectorDrawableCompat) mVectorDrawable.mutate();
586                    mVectorDrawable.setCallback(owner);
587                    mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
588                    mVectorDrawable.setAllowCaching(false);
589                }
590                if (copy.mAnimators != null) {
591                    final int numAnimators = copy.mAnimators.size();
592                    mAnimators = new ArrayList<Animator>(numAnimators);
593                    mTargetNameMap = new ArrayMap<Animator, String>(numAnimators);
594                    for (int i = 0; i < numAnimators; ++i) {
595                        Animator anim = copy.mAnimators.get(i);
596                        Animator animClone = anim.clone();
597                        String targetName = copy.mTargetNameMap.get(anim);
598                        Object targetObject = mVectorDrawable.getTargetByName(targetName);
599                        animClone.setTarget(targetObject);
600                        mAnimators.add(animClone);
601                        mTargetNameMap.put(animClone, targetName);
602                    }
603                }
604            }
605        }
606
607        @Override
608        public Drawable newDrawable() {
609            throw new IllegalStateException("No constant state support for SDK < 24.");
610        }
611
612        @Override
613        public Drawable newDrawable(Resources res) {
614            throw new IllegalStateException("No constant state support for SDK < 24.");
615        }
616
617        @Override
618        public int getChangingConfigurations() {
619            return mChangingConfigurations;
620        }
621    }
622
623    /**
624     * Utility function to fix color interpolation prior to Lollipop. Without this fix, colors
625     * are evaluated as raw integers instead of as colors, which leads to artifacts during
626     * fillColor animations.
627     */
628    private void setupColorAnimator(Animator animator) {
629        if (animator instanceof AnimatorSet) {
630            List<Animator> childAnimators = ((AnimatorSet) animator).getChildAnimations();
631            if (childAnimators != null) {
632                for (int i = 0; i < childAnimators.size(); ++i) {
633                    setupColorAnimator(childAnimators.get(i));
634                }
635            }
636        }
637        if (animator instanceof ObjectAnimator) {
638            ObjectAnimator objectAnim = (ObjectAnimator) animator;
639            final String propertyName = objectAnim.getPropertyName();
640            if ("fillColor".equals(propertyName) || "strokeColor".equals(propertyName)) {
641                if (mArgbEvaluator == null) {
642                    mArgbEvaluator = new ArgbEvaluator();
643                }
644                objectAnim.setEvaluator(mArgbEvaluator);
645            }
646        }
647    }
648
649    private void setupAnimatorsForTarget(String name, Animator animator) {
650        Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
651        animator.setTarget(target);
652        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
653            setupColorAnimator(animator);
654        }
655        if (mAnimatedVectorState.mAnimators == null) {
656            mAnimatedVectorState.mAnimators = new ArrayList<Animator>();
657            mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>();
658        }
659        mAnimatedVectorState.mAnimators.add(animator);
660        mAnimatedVectorState.mTargetNameMap.put(animator, name);
661        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
662            Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
663        }
664    }
665
666    @Override
667    public boolean isRunning() {
668        if (mDelegateDrawable != null) {
669            return ((AnimatedVectorDrawable) mDelegateDrawable).isRunning();
670        }
671        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
672        final int size = animators.size();
673        for (int i = 0; i < size; i++) {
674            final Animator animator = animators.get(i);
675            if (animator.isRunning()) {
676                return true;
677            }
678        }
679        return false;
680    }
681
682    private boolean isStarted() {
683        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
684        if (animators == null) {
685            return false;
686        }
687        final int size = animators.size();
688        for (int i = 0; i < size; i++) {
689            final Animator animator = animators.get(i);
690            if (animator.isRunning()) {
691                return true;
692            }
693        }
694        return false;
695    }
696
697    @Override
698    public void start() {
699        if (mDelegateDrawable != null) {
700            ((AnimatedVectorDrawable) mDelegateDrawable).start();
701            return;
702        }
703        // If any one of the animator has not ended, do nothing.
704        if (isStarted()) {
705            return;
706        }
707        // Otherwise, kick off every animator.
708        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
709        final int size = animators.size();
710        for (int i = 0; i < size; i++) {
711            final Animator animator = animators.get(i);
712            animator.start();
713        }
714        invalidateSelf();
715    }
716
717    @Override
718    public void stop() {
719        if (mDelegateDrawable != null) {
720            ((AnimatedVectorDrawable) mDelegateDrawable).stop();
721            return;
722        }
723        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
724        final int size = animators.size();
725        for (int i = 0; i < size; i++) {
726            final Animator animator = animators.get(i);
727            animator.end();
728        }
729    }
730
731    final Callback mCallback = new Callback() {
732        @Override
733        public void invalidateDrawable(Drawable who) {
734            invalidateSelf();
735        }
736
737        @Override
738        public void scheduleDrawable(Drawable who, Runnable what, long when) {
739            scheduleSelf(what, when);
740        }
741
742        @Override
743        public void unscheduleDrawable(Drawable who, Runnable what) {
744            unscheduleSelf(what);
745        }
746    };
747}
748