AnimatedVectorDrawableCompat.java revision 1f1b5540916924b36bc81cc3edb4f02245116dde
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 androidx.vectordrawable.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.content.Context;
23import android.content.res.ColorStateList;
24import android.content.res.Resources;
25import android.content.res.Resources.Theme;
26import android.content.res.TypedArray;
27import android.graphics.Canvas;
28import android.graphics.ColorFilter;
29import android.graphics.PorterDuff;
30import android.graphics.Rect;
31import android.graphics.drawable.Animatable;
32import android.graphics.drawable.AnimatedVectorDrawable;
33import android.graphics.drawable.Drawable;
34import android.os.Build;
35import android.util.AttributeSet;
36import android.util.Log;
37import android.util.Xml;
38
39import androidx.annotation.DrawableRes;
40import androidx.annotation.NonNull;
41import androidx.annotation.Nullable;
42import androidx.annotation.RequiresApi;
43import androidx.collection.ArrayMap;
44import androidx.core.content.res.ResourcesCompat;
45import androidx.core.content.res.TypedArrayUtils;
46import androidx.core.graphics.drawable.DrawableCompat;
47
48import org.xmlpull.v1.XmlPullParser;
49import org.xmlpull.v1.XmlPullParserException;
50
51import java.io.IOException;
52import java.util.ArrayList;
53import java.util.List;
54
55/**
56 * For API 24 and above, this class is delegating to the framework's {@link
57 * AnimatedVectorDrawable}.
58 * For older API version, this class uses {@link android.animation.ObjectAnimator} and
59 * {@link android.animation.AnimatorSet} to animate the properties of a
60 * {@link VectorDrawableCompat} to create an animated drawable.
61 * <p/>
62 * AnimatedVectorDrawableCompat are defined in the same XML format as
63 * {@link AnimatedVectorDrawable}.
64 * <p/>
65 * Here are all the animatable attributes in {@link VectorDrawableCompat}:
66 * <table border="2" align="center" cellpadding="5">
67 *     <thead>
68 *         <tr>
69 *             <th>Element Name</th>
70 *             <th>Animatable attribute name</th>
71 *         </tr>
72 *     </thead>
73 *     <tr>
74 *         <td>&lt;vector&gt;</td>
75 *         <td>alpha</td>
76 *     </tr>
77 *     <tr>
78 *         <td rowspan="7">&lt;group&gt;</td>
79 *         <td>rotation</td>
80 *     </tr>
81 *     <tr>
82 *         <td>pivotX</td>
83 *     </tr>
84 *     <tr>
85 *         <td>pivotY</td>
86 *     </tr>
87 *     <tr>
88 *         <td>scaleX</td>
89 *     </tr>
90 *     <tr>
91 *         <td>scaleY</td>
92 *     </tr>
93 *     <tr>
94 *         <td>translateX</td>
95 *     </tr>
96 *     <tr>
97 *         <td>translateY</td>
98 *     </tr>
99 *     <tr>
100 *         <td rowspan="8">&lt;path&gt;</td>
101 *         <td>fillColor</td>
102 *     </tr>
103 *     <tr>
104 *         <td>pathData</td>
105 *     </tr>
106 *     <tr>
107 *         <td>strokeColor</td>
108 *     </tr>
109 *     <tr>
110 *         <td>strokeWidth</td>
111 *     </tr>
112 *     <tr>
113 *         <td>strokeAlpha</td>
114 *     </tr>
115 *     <tr>
116 *         <td>fillAlpha</td>
117 *     </tr>
118 *     <tr>
119 *         <td>trimPathStart</td>
120 *     </tr>
121 *     <tr>
122 *         <td>trimPathEnd</td>
123 *     </tr>
124 *     <tr>
125 *         <td>trimPathOffset</td>
126 *     </tr>
127 * </table>
128 * <p/>
129 * You can always create a AnimatedVectorDrawableCompat object and use it as a Drawable by the Java
130 * API. In order to refer to AnimatedVectorDrawableCompat inside a XML file, you can use
131 * app:srcCompat attribute in AppCompat library's ImageButton or ImageView.
132 * <p/>
133 * Note that the animation in AnimatedVectorDrawableCompat now can support the following features:
134 * <ul>
135 * <li>Path Morphing (PathType evaluator). This is used for morphing one path into another.</li>
136 * <li>Path Interpolation. This is used to defined a flexible interpolator (represented as a path)
137 * instead of the system defined ones like LinearInterpolator.</li>
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
143public class AnimatedVectorDrawableCompat extends VectorDrawableCommon
144        implements Animatable2Compat {
145    private static final String LOGTAG = "AnimatedVDCompat";
146
147    private static final String ANIMATED_VECTOR = "animated-vector";
148    private static final String TARGET = "target";
149
150    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
151
152    private AnimatedVectorDrawableCompatState mAnimatedVectorState;
153
154    private Context mContext;
155
156    private ArgbEvaluator mArgbEvaluator = null;
157
158    AnimatedVectorDrawableDelegateState mCachedConstantStateDelegate;
159
160    // Use internal listener to support AVDC's callback.
161    private Animator.AnimatorListener mAnimatorListener = null;
162
163    // Use an array to keep track of multiple call back associated with one drawable.
164    private ArrayList<Animatable2Compat.AnimationCallback> mAnimationCallbacks = null;
165
166
167    AnimatedVectorDrawableCompat() {
168        this(null, null, null);
169    }
170
171    private AnimatedVectorDrawableCompat(@Nullable Context context) {
172        this(context, null, null);
173    }
174
175    private AnimatedVectorDrawableCompat(@Nullable Context context,
176            @Nullable AnimatedVectorDrawableCompatState state,
177            @Nullable Resources res) {
178        mContext = context;
179        if (state != null) {
180            mAnimatedVectorState = state;
181        } else {
182            mAnimatedVectorState = new AnimatedVectorDrawableCompatState(context, state, mCallback,
183                    res);
184        }
185    }
186
187    /**
188     * mutate() will be effective only if the getConstantState() is returning non-null.
189     * Otherwise, it just return the current object without modification.
190     */
191    @Override
192    public Drawable mutate() {
193        if (mDelegateDrawable != null) {
194            mDelegateDrawable.mutate();
195        }
196        // For older platforms that there is no delegated drawable, we just return this without
197        // any modification here, and the getConstantState() will return null in this case.
198        return this;
199    }
200
201
202    /**
203     * Create a AnimatedVectorDrawableCompat object.
204     *
205     * @param context the context for creating the animators.
206     * @param resId   the resource ID for AnimatedVectorDrawableCompat object.
207     * @return a new AnimatedVectorDrawableCompat or null if parsing error is found.
208     */
209    @Nullable
210    public static AnimatedVectorDrawableCompat create(@NonNull Context context,
211            @DrawableRes int resId) {
212        if (Build.VERSION.SDK_INT >= 24) {
213            final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
214            drawable.mDelegateDrawable = ResourcesCompat.getDrawable(context.getResources(), resId,
215                    context.getTheme());
216            drawable.mDelegateDrawable.setCallback(drawable.mCallback);
217            drawable.mCachedConstantStateDelegate = new AnimatedVectorDrawableDelegateState(
218                    drawable.mDelegateDrawable.getConstantState());
219            return drawable;
220        }
221        Resources resources = context.getResources();
222        try {
223            //noinspection AndroidLintResourceType - Parse drawable as XML.
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 && Build.VERSION.SDK_INT >= 24) {
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            DrawableCompat.setAutoMirrored(mDelegateDrawable, 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    @RequiresApi(24)
535    private static class AnimatedVectorDrawableDelegateState extends ConstantState {
536        private final ConstantState mDelegateState;
537
538        public AnimatedVectorDrawableDelegateState(ConstantState state) {
539            mDelegateState = state;
540        }
541
542        @Override
543        public Drawable newDrawable() {
544            AnimatedVectorDrawableCompat drawableCompat =
545                    new AnimatedVectorDrawableCompat();
546            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable();
547            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
548            return drawableCompat;
549        }
550
551        @Override
552        public Drawable newDrawable(Resources res) {
553            AnimatedVectorDrawableCompat drawableCompat =
554                    new AnimatedVectorDrawableCompat();
555            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res);
556            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
557            return drawableCompat;
558        }
559
560        @Override
561        public Drawable newDrawable(Resources res, Theme theme) {
562            AnimatedVectorDrawableCompat drawableCompat =
563                    new AnimatedVectorDrawableCompat();
564            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res, theme);
565            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
566            return drawableCompat;
567        }
568
569        @Override
570        public boolean canApplyTheme() {
571            return mDelegateState.canApplyTheme();
572        }
573
574        @Override
575        public int getChangingConfigurations() {
576            return mDelegateState.getChangingConfigurations();
577        }
578    }
579
580    private static class AnimatedVectorDrawableCompatState extends ConstantState {
581        int mChangingConfigurations;
582        VectorDrawableCompat mVectorDrawable;
583        // Combining the array of Animators into a single AnimatorSet to hook up listener easier.
584        AnimatorSet mAnimatorSet;
585        private ArrayList<Animator> mAnimators;
586        ArrayMap<Animator, String> mTargetNameMap;
587
588        public AnimatedVectorDrawableCompatState(Context context,
589                AnimatedVectorDrawableCompatState copy, Callback owner, Resources res) {
590            if (copy != null) {
591                mChangingConfigurations = copy.mChangingConfigurations;
592                if (copy.mVectorDrawable != null) {
593                    final ConstantState cs = copy.mVectorDrawable.getConstantState();
594                    if (res != null) {
595                        mVectorDrawable = (VectorDrawableCompat) cs.newDrawable(res);
596                    } else {
597                        mVectorDrawable = (VectorDrawableCompat) cs.newDrawable();
598                    }
599                    mVectorDrawable = (VectorDrawableCompat) mVectorDrawable.mutate();
600                    mVectorDrawable.setCallback(owner);
601                    mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
602                    mVectorDrawable.setAllowCaching(false);
603                }
604                if (copy.mAnimators != null) {
605                    final int numAnimators = copy.mAnimators.size();
606                    mAnimators = new ArrayList<Animator>(numAnimators);
607                    mTargetNameMap = new ArrayMap<Animator, String>(numAnimators);
608                    for (int i = 0; i < numAnimators; ++i) {
609                        Animator anim = copy.mAnimators.get(i);
610                        Animator animClone = anim.clone();
611                        String targetName = copy.mTargetNameMap.get(anim);
612                        Object targetObject = mVectorDrawable.getTargetByName(targetName);
613                        animClone.setTarget(targetObject);
614                        mAnimators.add(animClone);
615                        mTargetNameMap.put(animClone, targetName);
616                    }
617                    setupAnimatorSet();
618                }
619            }
620        }
621
622        @Override
623        public Drawable newDrawable() {
624            throw new IllegalStateException("No constant state support for SDK < 24.");
625        }
626
627        @Override
628        public Drawable newDrawable(Resources res) {
629            throw new IllegalStateException("No constant state support for SDK < 24.");
630        }
631
632        @Override
633        public int getChangingConfigurations() {
634            return mChangingConfigurations;
635        }
636
637        public void setupAnimatorSet() {
638            if (mAnimatorSet == null) {
639                mAnimatorSet = new AnimatorSet();
640            }
641            mAnimatorSet.playTogether(mAnimators);
642        }
643    }
644
645    /**
646     * Utility function to fix color interpolation prior to Lollipop. Without this fix, colors
647     * are evaluated as raw integers instead of as colors, which leads to artifacts during
648     * fillColor animations.
649     */
650    private void setupColorAnimator(Animator animator) {
651        if (animator instanceof AnimatorSet) {
652            List<Animator> childAnimators = ((AnimatorSet) animator).getChildAnimations();
653            if (childAnimators != null) {
654                for (int i = 0; i < childAnimators.size(); ++i) {
655                    setupColorAnimator(childAnimators.get(i));
656                }
657            }
658        }
659        if (animator instanceof ObjectAnimator) {
660            ObjectAnimator objectAnim = (ObjectAnimator) animator;
661            final String propertyName = objectAnim.getPropertyName();
662            if ("fillColor".equals(propertyName) || "strokeColor".equals(propertyName)) {
663                if (mArgbEvaluator == null) {
664                    mArgbEvaluator = new ArgbEvaluator();
665                }
666                objectAnim.setEvaluator(mArgbEvaluator);
667            }
668        }
669    }
670
671    private void setupAnimatorsForTarget(String name, Animator animator) {
672        Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
673        animator.setTarget(target);
674        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
675            setupColorAnimator(animator);
676        }
677        if (mAnimatedVectorState.mAnimators == null) {
678            mAnimatedVectorState.mAnimators = new ArrayList<Animator>();
679            mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>();
680        }
681        mAnimatedVectorState.mAnimators.add(animator);
682        mAnimatedVectorState.mTargetNameMap.put(animator, name);
683        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
684            Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
685        }
686    }
687
688    @Override
689    public boolean isRunning() {
690        if (mDelegateDrawable != null) {
691            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
692            return ((AnimatedVectorDrawable) mDelegateDrawable).isRunning();
693        }
694        return mAnimatedVectorState.mAnimatorSet.isRunning();
695    }
696
697    @Override
698    public void start() {
699        if (mDelegateDrawable != null) {
700            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
701            ((AnimatedVectorDrawable) mDelegateDrawable).start();
702            return;
703        }
704        // If any one of the animator has not ended, do nothing.
705        if (mAnimatedVectorState.mAnimatorSet.isStarted()) {
706            return;
707        }
708        // Otherwise, kick off animatorSet.
709        mAnimatedVectorState.mAnimatorSet.start();
710        invalidateSelf();
711    }
712
713    @Override
714    public void stop() {
715        if (mDelegateDrawable != null) {
716            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
717            ((AnimatedVectorDrawable) mDelegateDrawable).stop();
718            return;
719        }
720        mAnimatedVectorState.mAnimatorSet.end();
721    }
722
723    final Callback mCallback = new Callback() {
724        @Override
725        public void invalidateDrawable(Drawable who) {
726            invalidateSelf();
727        }
728
729        @Override
730        public void scheduleDrawable(Drawable who, Runnable what, long when) {
731            scheduleSelf(what, when);
732        }
733
734        @Override
735        public void unscheduleDrawable(Drawable who, Runnable what) {
736            unscheduleSelf(what);
737        }
738    };
739
740    /**
741     * A helper function to unregister the Animatable2Compat callback from the platform's
742     * Animatable2 callback, while keeping the internal array of callback up to date.
743     */
744    @RequiresApi(23)
745    private static boolean unregisterPlatformCallback(AnimatedVectorDrawable dr,
746            Animatable2Compat.AnimationCallback callback) {
747        return dr.unregisterAnimationCallback(callback.getPlatformCallback());
748    }
749
750    @Override
751    public void registerAnimationCallback(@NonNull Animatable2Compat.AnimationCallback
752            callback) {
753        if (mDelegateDrawable != null) {
754            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
755            registerPlatformCallback((AnimatedVectorDrawable) mDelegateDrawable, callback);
756            return;
757        }
758
759        if (callback == null) {
760            return;
761        }
762
763        // Add listener accordingly.
764        if (mAnimationCallbacks == null) {
765            mAnimationCallbacks = new ArrayList<>();
766        }
767
768        if (mAnimationCallbacks.contains(callback)) {
769            // If this call back is already in, then don't need to append another copy.
770            return;
771        }
772
773        mAnimationCallbacks.add(callback);
774
775        if (mAnimatorListener == null) {
776            // Create a animator listener and trigger the callback events when listener is
777            // triggered.
778            mAnimatorListener = new AnimatorListenerAdapter() {
779                @Override
780                public void onAnimationStart(Animator animation) {
781                    ArrayList<Animatable2Compat.AnimationCallback> tmpCallbacks =
782                            new ArrayList<>(mAnimationCallbacks);
783                    int size = tmpCallbacks.size();
784                    for (int i = 0; i < size; i++) {
785                        tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawableCompat.this);
786                    }
787                }
788
789                @Override
790                public void onAnimationEnd(Animator animation) {
791                    ArrayList<Animatable2Compat.AnimationCallback> tmpCallbacks =
792                            new ArrayList<>(mAnimationCallbacks);
793                    int size = tmpCallbacks.size();
794                    for (int i = 0; i < size; i++) {
795                        tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawableCompat.this);
796                    }
797                }
798            };
799        }
800        mAnimatedVectorState.mAnimatorSet.addListener(mAnimatorListener);
801    }
802
803    /**
804     * A helper function to register the Animatable2Compat callback on the platform's Animatable2
805     * callback.
806     */
807    @RequiresApi(23)
808    private static void registerPlatformCallback(@NonNull AnimatedVectorDrawable avd,
809            @NonNull final Animatable2Compat.AnimationCallback callback) {
810        avd.registerAnimationCallback(callback.getPlatformCallback());
811    }
812
813    /**
814     * A helper function to clean up the animator listener in the mAnimatorSet.
815     */
816    private void removeAnimatorSetListener() {
817        if (mAnimatorListener != null) {
818            mAnimatedVectorState.mAnimatorSet.removeListener(mAnimatorListener);
819            mAnimatorListener = null;
820        }
821    }
822
823    @Override
824    public boolean unregisterAnimationCallback(
825            @NonNull Animatable2Compat.AnimationCallback callback) {
826        if (mDelegateDrawable != null) {
827            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
828            unregisterPlatformCallback((AnimatedVectorDrawable) mDelegateDrawable, callback);
829        }
830
831        if (mAnimationCallbacks == null || callback == null) {
832            // Nothing to be removed.
833            return false;
834        }
835        boolean removed = mAnimationCallbacks.remove(callback);
836
837        //  When the last call back unregistered, remove the listener accordingly.
838        if (mAnimationCallbacks.size() == 0) {
839            removeAnimatorSetListener();
840        }
841        return removed;
842    }
843
844    @Override
845    public void clearAnimationCallbacks() {
846        if (mDelegateDrawable != null) {
847            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
848            ((AnimatedVectorDrawable) mDelegateDrawable).clearAnimationCallbacks();
849            return;
850        }
851        removeAnimatorSetListener();
852        if (mAnimationCallbacks == null) {
853            return;
854        }
855
856        mAnimationCallbacks.clear();
857    }
858
859    /**
860     * Utility function to register callback to Drawable, when the drawable is created from XML and
861     * referred in Java code, e.g: ImageView.getDrawable().
862     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
863     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
864     */
865    public static void registerAnimationCallback(Drawable dr,
866            Animatable2Compat.AnimationCallback callback) {
867        if (dr == null || callback == null) {
868            return;
869        }
870        if (!(dr instanceof Animatable)) {
871            return;
872        }
873
874        if (Build.VERSION.SDK_INT >= 24) {
875            registerPlatformCallback((AnimatedVectorDrawable) dr, callback);
876        } else {
877            ((AnimatedVectorDrawableCompat) dr).registerAnimationCallback(callback);
878        }
879    }
880
881    /**
882     * Utility function to unregister animation callback from Drawable, when the drawable is
883     * created from XML and referred in Java code, e.g: ImageView.getDrawable().
884     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
885     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
886     */
887    public static boolean unregisterAnimationCallback(Drawable dr,
888            Animatable2Compat.AnimationCallback callback) {
889        if (dr == null || callback == null) {
890            return false;
891        }
892        if (!(dr instanceof Animatable)) {
893            return false;
894        }
895
896        if (Build.VERSION.SDK_INT >= 24) {
897            return unregisterPlatformCallback((AnimatedVectorDrawable) dr, callback);
898        } else {
899            return ((AnimatedVectorDrawableCompat) dr).unregisterAnimationCallback(callback);
900        }
901    }
902
903    /**
904     * Utility function to clear animation callbacks from Drawable, when the drawable is
905     * created from XML and referred in Java code, e.g: ImageView.getDrawable().
906     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
907     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
908     */
909    public static void clearAnimationCallbacks(Drawable dr) {
910        if (dr == null || !(dr instanceof Animatable)) {
911            return;
912        }
913        if (Build.VERSION.SDK_INT >= 24) {
914            ((AnimatedVectorDrawable) dr).clearAnimationCallbacks();
915        } else {
916            ((AnimatedVectorDrawableCompat) dr).clearAnimationCallbacks();
917        }
918
919    }
920}
921