AnimatedVectorDrawableCompat.java revision b6086751979cb14740815502597e9fcfddb7054a
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.AnimatorListenerAdapter;
19import android.animation.AnimatorSet;
20import android.animation.ArgbEvaluator;
21import android.animation.ObjectAnimator;
22import android.annotation.SuppressLint;
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.content.res.TypedArrayUtils;
41import android.support.v4.graphics.drawable.DrawableCompat;
42import android.support.v4.util.ArrayMap;
43import android.util.AttributeSet;
44import android.util.Log;
45import android.util.Xml;
46
47import org.xmlpull.v1.XmlPullParser;
48import org.xmlpull.v1.XmlPullParserException;
49
50import java.io.IOException;
51import java.util.ArrayList;
52import java.util.List;
53
54/**
55 * For API 24 and above, this class is delegating to the framework's {@link
56 * AnimatedVectorDrawable}.
57 * For older API version, this class uses {@link android.animation.ObjectAnimator} and
58 * {@link android.animation.AnimatorSet} to animate the properties of a
59 * {@link VectorDrawableCompat} to create an animated drawable.
60 * <p/>
61 * AnimatedVectorDrawableCompat are defined in the same XML format as
62 * {@link AnimatedVectorDrawable}.
63 * <p/>
64 * Here are all the animatable attributes in {@link VectorDrawableCompat}:
65 * <table border="2" align="center" cellpadding="5">
66 *     <thead>
67 *         <tr>
68 *             <th>Element Name</th>
69 *             <th>Animatable attribute name</th>
70 *         </tr>
71 *     </thead>
72 *     <tr>
73 *         <td>&lt;vector&gt;</td>
74 *         <td>alpha</td>
75 *     </tr>
76 *     <tr>
77 *         <td rowspan="7">&lt;group&gt;</td>
78 *         <td>rotation</td>
79 *     </tr>
80 *     <tr>
81 *         <td>pivotX</td>
82 *     </tr>
83 *     <tr>
84 *         <td>pivotY</td>
85 *     </tr>
86 *     <tr>
87 *         <td>scaleX</td>
88 *     </tr>
89 *     <tr>
90 *         <td>scaleY</td>
91 *     </tr>
92 *     <tr>
93 *         <td>translateX</td>
94 *     </tr>
95 *     <tr>
96 *         <td>translateY</td>
97 *     </tr>
98 *     <tr>
99 *         <td rowspan="8">&lt;path&gt;</td>
100 *         <td>fillColor</td>
101 *     </tr>
102 *     <tr>
103 *         <td>pathData</td>
104 *     </tr>
105 *     <tr>
106 *         <td>strokeColor</td>
107 *     </tr>
108 *     <tr>
109 *         <td>strokeWidth</td>
110 *     </tr>
111 *     <tr>
112 *         <td>strokeAlpha</td>
113 *     </tr>
114 *     <tr>
115 *         <td>fillAlpha</td>
116 *     </tr>
117 *     <tr>
118 *         <td>trimPathStart</td>
119 *     </tr>
120 *     <tr>
121 *         <td>trimPathOffset</td>
122 *     </tr>
123 * </table>
124 * <p/>
125 * You can always create a AnimatedVectorDrawableCompat object and use it as a Drawable by the Java
126 * API. In order to refer to AnimatedVectorDrawableCompat inside a XML file, you can use
127 * app:srcCompat attribute in AppCompat library's ImageButton or ImageView.
128 * <p/>
129 * Note that the animation in AnimatedVectorDrawableCompat now can support the following features:
130 * <ul>
131 * <li>Path Morphing (PathType evaluator). This is used for morphing one path into another.</li>
132 * <li>Path Interpolation. This is used to defined a flexible interpolator (represented as a path)
133 * instead of the system defined ones like LinearInterpolator.</li>
134 * </ul>
135 * <p/>
136 * But not support this one feature yet:
137 * <ul>
138 * <li>Animating 2 values in one ObjectAnimator according to one path's X value and Y value. One
139 * usage is moving one object in both X and Y dimensions along an path.</li> *
140 * </ul>
141 */
142
143@SuppressLint("NewApi")
144public class AnimatedVectorDrawableCompat extends VectorDrawableCommon
145        implements Animatable2Compat {
146    private static final String LOGTAG = "AnimatedVDCompat";
147
148    private static final String ANIMATED_VECTOR = "animated-vector";
149    private static final String TARGET = "target";
150
151    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
152
153    private AnimatedVectorDrawableCompatState mAnimatedVectorState;
154
155    private Context mContext;
156
157    private ArgbEvaluator mArgbEvaluator = null;
158
159    AnimatedVectorDrawableDelegateState mCachedConstantStateDelegate;
160
161    // Use internal listener to support AVDC's callback.
162    private Animator.AnimatorListener mAnimatorListener = null;
163
164    // Use an array to keep track of multiple call back associated with one drawable.
165    private ArrayList<Animatable2Compat.AnimationCallback> mAnimationCallbacks = null;
166
167
168    AnimatedVectorDrawableCompat() {
169        this(null, null, null);
170    }
171
172    private AnimatedVectorDrawableCompat(@Nullable Context context) {
173        this(context, null, null);
174    }
175
176    private AnimatedVectorDrawableCompat(@Nullable Context context,
177            @Nullable AnimatedVectorDrawableCompatState state,
178            @Nullable Resources res) {
179        mContext = context;
180        if (state != null) {
181            mAnimatedVectorState = state;
182        } else {
183            mAnimatedVectorState = new AnimatedVectorDrawableCompatState(context, state, mCallback,
184                    res);
185        }
186    }
187
188    /**
189     * mutate() will be effective only if the getConstantState() is returning non-null.
190     * Otherwise, it just return the current object without modification.
191     */
192    @Override
193    public Drawable mutate() {
194        if (mDelegateDrawable != null) {
195            mDelegateDrawable.mutate();
196        }
197        // For older platforms that there is no delegated drawable, we just return this without
198        // any modification here, and the getConstantState() will return null in this case.
199        return this;
200    }
201
202
203    /**
204     * Create a AnimatedVectorDrawableCompat object.
205     *
206     * @param context the context for creating the animators.
207     * @param resId   the resource ID for AnimatedVectorDrawableCompat object.
208     * @return a new AnimatedVectorDrawableCompat or null if parsing error is found.
209     */
210    @Nullable
211    public static AnimatedVectorDrawableCompat create(@NonNull Context context,
212            @DrawableRes int resId) {
213        if (Build.VERSION.SDK_INT >= 24) {
214            final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
215            drawable.mDelegateDrawable = ResourcesCompat.getDrawable(context.getResources(), resId,
216                    context.getTheme());
217            drawable.mDelegateDrawable.setCallback(drawable.mCallback);
218            drawable.mCachedConstantStateDelegate = new AnimatedVectorDrawableDelegateState(
219                    drawable.mDelegateDrawable.getConstantState());
220            return drawable;
221        }
222        Resources resources = context.getResources();
223        try {
224            final XmlPullParser parser = resources.getXml(resId);
225            final AttributeSet attrs = Xml.asAttributeSet(parser);
226            int type;
227            while ((type = parser.next()) != XmlPullParser.START_TAG
228                    && type != XmlPullParser.END_DOCUMENT) {
229                // Empty loop
230            }
231            if (type != XmlPullParser.START_TAG) {
232                throw new XmlPullParserException("No start tag found");
233            }
234            return createFromXmlInner(context, context.getResources(), parser, attrs,
235                    context.getTheme());
236        } catch (XmlPullParserException e) {
237            Log.e(LOGTAG, "parser error", e);
238        } catch (IOException e) {
239            Log.e(LOGTAG, "parser error", e);
240        }
241        return null;
242    }
243
244    /**
245     * Create a AnimatedVectorDrawableCompat from inside an XML document using an optional
246     * {@link Theme}. Called on a parser positioned at a tag in an XML
247     * document, tries to create a Drawable from that tag. Returns {@code null}
248     * if the tag is not a valid drawable.
249     */
250    public static AnimatedVectorDrawableCompat createFromXmlInner(Context context, Resources r,
251            XmlPullParser parser, AttributeSet attrs, Theme theme)
252            throws XmlPullParserException, IOException {
253        final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
254        drawable.inflate(r, parser, attrs, theme);
255        return drawable;
256    }
257
258    /**
259     * {@inheritDoc}
260     * <strong>Note</strong> that we don't support constant state when SDK < 24.
261     * Make sure you check the return value before using it.
262     */
263    @Override
264    public ConstantState getConstantState() {
265        if (mDelegateDrawable != null) {
266            return new AnimatedVectorDrawableDelegateState(mDelegateDrawable.getConstantState());
267        }
268        // We can't support constant state in older platform.
269        // We need Context to create the animator, and we can't save the context in the constant
270        // state.
271        return null;
272    }
273
274    @Override
275    public int getChangingConfigurations() {
276        if (mDelegateDrawable != null) {
277            return mDelegateDrawable.getChangingConfigurations();
278        }
279        return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations;
280    }
281
282    @Override
283    public void draw(Canvas canvas) {
284        if (mDelegateDrawable != null) {
285            mDelegateDrawable.draw(canvas);
286            return;
287        }
288        mAnimatedVectorState.mVectorDrawable.draw(canvas);
289        if (mAnimatedVectorState.mAnimatorSet.isStarted()) {
290            invalidateSelf();
291        }
292    }
293
294    @Override
295    protected void onBoundsChange(Rect bounds) {
296        if (mDelegateDrawable != null) {
297            mDelegateDrawable.setBounds(bounds);
298            return;
299        }
300        mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
301    }
302
303    @Override
304    protected boolean onStateChange(int[] state) {
305        if (mDelegateDrawable != null) {
306            return mDelegateDrawable.setState(state);
307        }
308        return mAnimatedVectorState.mVectorDrawable.setState(state);
309    }
310
311    @Override
312    protected boolean onLevelChange(int level) {
313        if (mDelegateDrawable != null) {
314            return mDelegateDrawable.setLevel(level);
315        }
316        return mAnimatedVectorState.mVectorDrawable.setLevel(level);
317    }
318
319    @Override
320    public int getAlpha() {
321        if (mDelegateDrawable != null) {
322            return DrawableCompat.getAlpha(mDelegateDrawable);
323        }
324        return mAnimatedVectorState.mVectorDrawable.getAlpha();
325    }
326
327    @Override
328    public void setAlpha(int alpha) {
329        if (mDelegateDrawable != null) {
330            mDelegateDrawable.setAlpha(alpha);
331            return;
332        }
333        mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
334    }
335
336    @Override
337    public void setColorFilter(ColorFilter colorFilter) {
338        if (mDelegateDrawable != null) {
339            mDelegateDrawable.setColorFilter(colorFilter);
340            return;
341        }
342        mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
343    }
344
345    @Override
346    public void setTint(int tint) {
347        if (mDelegateDrawable != null) {
348            DrawableCompat.setTint(mDelegateDrawable, tint);
349            return;
350        }
351
352        mAnimatedVectorState.mVectorDrawable.setTint(tint);
353    }
354
355    @Override
356    public void setTintList(ColorStateList tint) {
357        if (mDelegateDrawable != null) {
358            DrawableCompat.setTintList(mDelegateDrawable, tint);
359            return;
360        }
361
362        mAnimatedVectorState.mVectorDrawable.setTintList(tint);
363    }
364
365    @Override
366    public void setTintMode(PorterDuff.Mode tintMode) {
367        if (mDelegateDrawable != null) {
368            DrawableCompat.setTintMode(mDelegateDrawable, tintMode);
369            return;
370        }
371
372        mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
373    }
374
375    @Override
376    public boolean setVisible(boolean visible, boolean restart) {
377        if (mDelegateDrawable != null) {
378            return mDelegateDrawable.setVisible(visible, restart);
379        }
380        mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
381        return super.setVisible(visible, restart);
382    }
383
384    @Override
385    public boolean isStateful() {
386        if (mDelegateDrawable != null) {
387            return mDelegateDrawable.isStateful();
388        }
389        return mAnimatedVectorState.mVectorDrawable.isStateful();
390    }
391
392    @Override
393    public int getOpacity() {
394        if (mDelegateDrawable != null) {
395            return mDelegateDrawable.getOpacity();
396        }
397        return mAnimatedVectorState.mVectorDrawable.getOpacity();
398    }
399
400    @Override
401    public int getIntrinsicWidth() {
402        if (mDelegateDrawable != null) {
403            return mDelegateDrawable.getIntrinsicWidth();
404        }
405        return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
406    }
407
408    @Override
409    public int getIntrinsicHeight() {
410        if (mDelegateDrawable != null) {
411            return mDelegateDrawable.getIntrinsicHeight();
412        }
413        return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
414    }
415
416    @Override
417    public boolean isAutoMirrored() {
418        if (mDelegateDrawable != null) {
419            return DrawableCompat.isAutoMirrored(mDelegateDrawable);
420        }
421        return mAnimatedVectorState.mVectorDrawable.isAutoMirrored();
422    }
423
424    @Override
425    public void setAutoMirrored(boolean mirrored) {
426        if (mDelegateDrawable != null) {
427            mDelegateDrawable.setAutoMirrored(mirrored);
428            return;
429        }
430        mAnimatedVectorState.mVectorDrawable.setAutoMirrored(mirrored);
431    }
432
433    @Override
434    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
435            throws XmlPullParserException, IOException {
436        if (mDelegateDrawable != null) {
437            DrawableCompat.inflate(mDelegateDrawable, res, parser, attrs, theme);
438            return;
439        }
440        int eventType = parser.getEventType();
441        final int innerDepth = parser.getDepth() + 1;
442
443        // Parse everything until the end of the animated-vector element.
444        while (eventType != XmlPullParser.END_DOCUMENT
445                && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
446            if (eventType == XmlPullParser.START_TAG) {
447                final String tagName = parser.getName();
448                if (DBG_ANIMATION_VECTOR_DRAWABLE) {
449                    Log.v(LOGTAG, "tagName is " + tagName);
450                }
451                if (ANIMATED_VECTOR.equals(tagName)) {
452                    final TypedArray a =
453                            TypedArrayUtils.obtainAttributes(res, theme, attrs,
454                                    AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE);
455
456                    int drawableRes = a.getResourceId(
457                            AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_DRAWABLE, 0);
458                    if (DBG_ANIMATION_VECTOR_DRAWABLE) {
459                        Log.v(LOGTAG, "drawableRes is " + drawableRes);
460                    }
461                    if (drawableRes != 0) {
462                        VectorDrawableCompat vectorDrawable = VectorDrawableCompat.create(res,
463                                drawableRes, theme);
464                        vectorDrawable.setAllowCaching(false);
465                        vectorDrawable.setCallback(mCallback);
466                        if (mAnimatedVectorState.mVectorDrawable != null) {
467                            mAnimatedVectorState.mVectorDrawable.setCallback(null);
468                        }
469                        mAnimatedVectorState.mVectorDrawable = vectorDrawable;
470                    }
471                    a.recycle();
472                } else if (TARGET.equals(tagName)) {
473                    final TypedArray a =
474                            res.obtainAttributes(attrs,
475                                    AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET);
476                    final String target = a.getString(
477                            AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET_NAME);
478
479                    int id = a.getResourceId(
480                            AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET_ANIMATION,
481                            0);
482                    if (id != 0) {
483                        if (mContext != null) {
484                            // There are some important features (like path morphing), added into
485                            // Animator code to support AVD at API 21.
486                            Animator objectAnimator = AnimatorInflaterCompat.loadAnimator(
487                                    mContext, id);
488                            setupAnimatorsForTarget(target, objectAnimator);
489                        } else {
490                            a.recycle();
491                            throw new IllegalStateException("Context can't be null when inflating" +
492                                    " animators");
493                        }
494                    }
495                    a.recycle();
496                }
497            }
498            eventType = parser.next();
499        }
500
501        mAnimatedVectorState.setupAnimatorSet();
502    }
503
504    @Override
505    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs)
506            throws XmlPullParserException, IOException {
507        inflate(res, parser, attrs, null);
508    }
509
510    @Override
511    public void applyTheme(Theme t) {
512        if (mDelegateDrawable != null) {
513            DrawableCompat.applyTheme(mDelegateDrawable, t);
514            return;
515        }
516        // TODO: support theming in older platform.
517        return;
518    }
519
520    @Override
521    public boolean canApplyTheme() {
522        if (mDelegateDrawable != null) {
523            return DrawableCompat.canApplyTheme(mDelegateDrawable);
524        }
525        // TODO: support theming in older platform.
526        return false;
527    }
528
529    /**
530     * Constant state for delegating the creating drawable job.
531     * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains
532     * a delegated VectorDrawable instance.
533     */
534    private static class AnimatedVectorDrawableDelegateState extends ConstantState {
535        private final ConstantState mDelegateState;
536
537        public AnimatedVectorDrawableDelegateState(ConstantState state) {
538            mDelegateState = state;
539        }
540
541        @Override
542        public Drawable newDrawable() {
543            AnimatedVectorDrawableCompat drawableCompat =
544                    new AnimatedVectorDrawableCompat();
545            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable();
546            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
547            return drawableCompat;
548        }
549
550        @Override
551        public Drawable newDrawable(Resources res) {
552            AnimatedVectorDrawableCompat drawableCompat =
553                    new AnimatedVectorDrawableCompat();
554            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res);
555            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
556            return drawableCompat;
557        }
558
559        @Override
560        public Drawable newDrawable(Resources res, Theme theme) {
561            AnimatedVectorDrawableCompat drawableCompat =
562                    new AnimatedVectorDrawableCompat();
563            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res, theme);
564            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
565            return drawableCompat;
566        }
567
568        @Override
569        public boolean canApplyTheme() {
570            return mDelegateState.canApplyTheme();
571        }
572
573        @Override
574        public int getChangingConfigurations() {
575            return mDelegateState.getChangingConfigurations();
576        }
577    }
578
579    private static class AnimatedVectorDrawableCompatState extends ConstantState {
580        int mChangingConfigurations;
581        VectorDrawableCompat mVectorDrawable;
582        // Combining the array of Animators into a single AnimatorSet to hook up listener easier.
583        AnimatorSet mAnimatorSet;
584        private ArrayList<Animator> mAnimators;
585        ArrayMap<Animator, String> mTargetNameMap;
586
587        public AnimatedVectorDrawableCompatState(Context context,
588                AnimatedVectorDrawableCompatState copy, Callback owner, Resources res) {
589            if (copy != null) {
590                mChangingConfigurations = copy.mChangingConfigurations;
591                if (copy.mVectorDrawable != null) {
592                    final ConstantState cs = copy.mVectorDrawable.getConstantState();
593                    if (res != null) {
594                        mVectorDrawable = (VectorDrawableCompat) cs.newDrawable(res);
595                    } else {
596                        mVectorDrawable = (VectorDrawableCompat) cs.newDrawable();
597                    }
598                    mVectorDrawable = (VectorDrawableCompat) mVectorDrawable.mutate();
599                    mVectorDrawable.setCallback(owner);
600                    mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
601                    mVectorDrawable.setAllowCaching(false);
602                }
603                if (copy.mAnimators != null) {
604                    final int numAnimators = copy.mAnimators.size();
605                    mAnimators = new ArrayList<Animator>(numAnimators);
606                    mTargetNameMap = new ArrayMap<Animator, String>(numAnimators);
607                    for (int i = 0; i < numAnimators; ++i) {
608                        Animator anim = copy.mAnimators.get(i);
609                        Animator animClone = anim.clone();
610                        String targetName = copy.mTargetNameMap.get(anim);
611                        Object targetObject = mVectorDrawable.getTargetByName(targetName);
612                        animClone.setTarget(targetObject);
613                        mAnimators.add(animClone);
614                        mTargetNameMap.put(animClone, targetName);
615                    }
616                    setupAnimatorSet();
617                }
618            }
619        }
620
621        @Override
622        public Drawable newDrawable() {
623            throw new IllegalStateException("No constant state support for SDK < 24.");
624        }
625
626        @Override
627        public Drawable newDrawable(Resources res) {
628            throw new IllegalStateException("No constant state support for SDK < 24.");
629        }
630
631        @Override
632        public int getChangingConfigurations() {
633            return mChangingConfigurations;
634        }
635
636        public void setupAnimatorSet() {
637            if (mAnimatorSet == null) {
638                mAnimatorSet = new AnimatorSet();
639            }
640            mAnimatorSet.playTogether(mAnimators);
641        }
642    }
643
644    /**
645     * Utility function to fix color interpolation prior to Lollipop. Without this fix, colors
646     * are evaluated as raw integers instead of as colors, which leads to artifacts during
647     * fillColor animations.
648     */
649    private void setupColorAnimator(Animator animator) {
650        if (animator instanceof AnimatorSet) {
651            List<Animator> childAnimators = ((AnimatorSet) animator).getChildAnimations();
652            if (childAnimators != null) {
653                for (int i = 0; i < childAnimators.size(); ++i) {
654                    setupColorAnimator(childAnimators.get(i));
655                }
656            }
657        }
658        if (animator instanceof ObjectAnimator) {
659            ObjectAnimator objectAnim = (ObjectAnimator) animator;
660            final String propertyName = objectAnim.getPropertyName();
661            if ("fillColor".equals(propertyName) || "strokeColor".equals(propertyName)) {
662                if (mArgbEvaluator == null) {
663                    mArgbEvaluator = new ArgbEvaluator();
664                }
665                objectAnim.setEvaluator(mArgbEvaluator);
666            }
667        }
668    }
669
670    private void setupAnimatorsForTarget(String name, Animator animator) {
671        Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
672        animator.setTarget(target);
673        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
674            setupColorAnimator(animator);
675        }
676        if (mAnimatedVectorState.mAnimators == null) {
677            mAnimatedVectorState.mAnimators = new ArrayList<Animator>();
678            mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>();
679        }
680        mAnimatedVectorState.mAnimators.add(animator);
681        mAnimatedVectorState.mTargetNameMap.put(animator, name);
682        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
683            Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
684        }
685    }
686
687    @Override
688    public boolean isRunning() {
689        if (mDelegateDrawable != null) {
690            return ((AnimatedVectorDrawable) mDelegateDrawable).isRunning();
691        }
692        return mAnimatedVectorState.mAnimatorSet.isRunning();
693    }
694
695    @Override
696    public void start() {
697        if (mDelegateDrawable != null) {
698            ((AnimatedVectorDrawable) mDelegateDrawable).start();
699            return;
700        }
701        // If any one of the animator has not ended, do nothing.
702        if (mAnimatedVectorState.mAnimatorSet.isStarted()) {
703            return;
704        }
705        // Otherwise, kick off animatorSet.
706        mAnimatedVectorState.mAnimatorSet.start();
707        invalidateSelf();
708    }
709
710    @Override
711    public void stop() {
712        if (mDelegateDrawable != null) {
713            ((AnimatedVectorDrawable) mDelegateDrawable).stop();
714            return;
715        }
716        mAnimatedVectorState.mAnimatorSet.end();
717    }
718
719    final Callback mCallback = new Callback() {
720        @Override
721        public void invalidateDrawable(Drawable who) {
722            invalidateSelf();
723        }
724
725        @Override
726        public void scheduleDrawable(Drawable who, Runnable what, long when) {
727            scheduleSelf(what, when);
728        }
729
730        @Override
731        public void unscheduleDrawable(Drawable who, Runnable what) {
732            unscheduleSelf(what);
733        }
734    };
735
736    /**
737     * A helper function to unregister the Animatable2Compat callback from the platform's
738     * Animatable2 callback, while keeping the internal array of callback up to date.
739     */
740    private static boolean unregisterPlatformCallback(AnimatedVectorDrawable dr,
741            Animatable2Compat.AnimationCallback callback) {
742        return dr.unregisterAnimationCallback(callback.getPlatformCallback());
743    }
744
745    @Override
746    public void registerAnimationCallback(@NonNull Animatable2Compat.AnimationCallback
747            callback) {
748        if (mDelegateDrawable != null) {
749            registerPlatformCallback((AnimatedVectorDrawable) mDelegateDrawable, callback);
750            return;
751        }
752
753        if (callback == null) {
754            return;
755        }
756
757        // Add listener accordingly.
758        if (mAnimationCallbacks == null) {
759            mAnimationCallbacks = new ArrayList<>();
760        }
761
762        if (mAnimationCallbacks.contains(callback)) {
763            // If this call back is already in, then don't need to append another copy.
764            return;
765        }
766
767        mAnimationCallbacks.add(callback);
768
769        if (mAnimatorListener == null) {
770            // Create a animator listener and trigger the callback events when listener is
771            // triggered.
772            mAnimatorListener = new AnimatorListenerAdapter() {
773                @Override
774                public void onAnimationStart(Animator animation) {
775                    ArrayList<Animatable2Compat.AnimationCallback> tmpCallbacks =
776                            new ArrayList<>(mAnimationCallbacks);
777                    int size = tmpCallbacks.size();
778                    for (int i = 0; i < size; i++) {
779                        tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawableCompat.this);
780                    }
781                }
782
783                @Override
784                public void onAnimationEnd(Animator animation) {
785                    ArrayList<Animatable2Compat.AnimationCallback> tmpCallbacks =
786                            new ArrayList<>(mAnimationCallbacks);
787                    int size = tmpCallbacks.size();
788                    for (int i = 0; i < size; i++) {
789                        tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawableCompat.this);
790                    }
791                }
792            };
793        }
794        mAnimatedVectorState.mAnimatorSet.addListener(mAnimatorListener);
795    }
796
797    /**
798     * A helper function to register the Animatable2Compat callback on the platform's Animatable2
799     * callback.
800     */
801    private static void registerPlatformCallback(@NonNull AnimatedVectorDrawable avd,
802            @NonNull final Animatable2Compat.AnimationCallback callback) {
803        avd.registerAnimationCallback(callback.getPlatformCallback());
804    }
805
806    /**
807     * A helper function to clean up the animator listener in the mAnimatorSet.
808     */
809    private void removeAnimatorSetListener() {
810        if (mAnimatorListener != null) {
811            mAnimatedVectorState.mAnimatorSet.removeListener(mAnimatorListener);
812            mAnimatorListener = null;
813        }
814    }
815
816    @Override
817    public boolean unregisterAnimationCallback(
818            @NonNull Animatable2Compat.AnimationCallback callback) {
819        if (mDelegateDrawable != null) {
820            unregisterPlatformCallback((AnimatedVectorDrawable) mDelegateDrawable, callback);
821        }
822
823        if (mAnimationCallbacks == null || callback == null) {
824            // Nothing to be removed.
825            return false;
826        }
827        boolean removed = mAnimationCallbacks.remove(callback);
828
829        //  When the last call back unregistered, remove the listener accordingly.
830        if (mAnimationCallbacks.size() == 0) {
831            removeAnimatorSetListener();
832        }
833        return removed;
834    }
835
836    @Override
837    public void clearAnimationCallbacks() {
838        if (mDelegateDrawable != null) {
839            ((AnimatedVectorDrawable) mDelegateDrawable).clearAnimationCallbacks();
840            return;
841        }
842        removeAnimatorSetListener();
843        if (mAnimationCallbacks == null) {
844            return;
845        }
846
847        mAnimationCallbacks.clear();
848    }
849
850    /**
851     * Utility function to register callback to Drawable, when the drawable is created from XML and
852     * referred in Java code, e.g: ImageView.getDrawable().
853     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
854     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
855     */
856    public static void registerAnimationCallback(Drawable dr,
857            Animatable2Compat.AnimationCallback callback) {
858        if (dr == null || callback == null) {
859            return;
860        }
861        if (!(dr instanceof Animatable)) {
862            return;
863        }
864
865        if (Build.VERSION.SDK_INT >= 24) {
866            registerPlatformCallback((AnimatedVectorDrawable) dr, callback);
867        } else {
868            ((AnimatedVectorDrawableCompat) dr).registerAnimationCallback(callback);
869        }
870    }
871
872    /**
873     * Utility function to unregister animation callback from Drawable, when the drawable is
874     * created from XML and referred in Java code, e.g: ImageView.getDrawable().
875     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
876     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
877     */
878    public static boolean unregisterAnimationCallback(Drawable dr,
879            Animatable2Compat.AnimationCallback callback) {
880        if (dr == null || callback == null) {
881            return false;
882        }
883        if (!(dr instanceof Animatable)) {
884            return false;
885        }
886
887        if (Build.VERSION.SDK_INT >= 24) {
888            return unregisterPlatformCallback((AnimatedVectorDrawable) dr, callback);
889        } else {
890            return ((AnimatedVectorDrawableCompat) dr).unregisterAnimationCallback(callback);
891        }
892    }
893
894    /**
895     * Utility function to clear animation callbacks from Drawable, when the drawable is
896     * created from XML and referred in Java code, e.g: ImageView.getDrawable().
897     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
898     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
899     */
900    public static void clearAnimationCallbacks(Drawable dr) {
901        if (dr == null || !(dr instanceof Animatable)) {
902            return;
903        }
904        if (Build.VERSION.SDK_INT >= 24) {
905            ((AnimatedVectorDrawable) dr).clearAnimationCallbacks();
906        } else {
907            ((AnimatedVectorDrawableCompat) dr).clearAnimationCallbacks();
908        }
909
910    }
911}
912