AnimatedVectorDrawableCompat.java revision ac5fe7c617c66850fff75a9fce9979c6e5674b0f
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 androidx.annotation.DrawableRes;
36import androidx.annotation.NonNull;
37import androidx.annotation.Nullable;
38import androidx.annotation.RequiresApi;
39import androidx.core.content.res.ResourcesCompat;
40import androidx.core.content.res.TypedArrayUtils;
41import androidx.core.graphics.drawable.DrawableCompat;
42import androidx.collection.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>trimPathEnd</td>
122 *     </tr>
123 *     <tr>
124 *         <td>trimPathOffset</td>
125 *     </tr>
126 * </table>
127 * <p/>
128 * You can always create a AnimatedVectorDrawableCompat object and use it as a Drawable by the Java
129 * API. In order to refer to AnimatedVectorDrawableCompat inside a XML file, you can use
130 * app:srcCompat attribute in AppCompat library's ImageButton or ImageView.
131 * <p/>
132 * Note that the animation in AnimatedVectorDrawableCompat now can support the following features:
133 * <ul>
134 * <li>Path Morphing (PathType evaluator). This is used for morphing one path into another.</li>
135 * <li>Path Interpolation. This is used to defined a flexible interpolator (represented as a path)
136 * instead of the system defined ones like LinearInterpolator.</li>
137 * <li>Animating 2 values in one ObjectAnimator according to one path's X value and Y value. One
138 * usage is moving one object in both X and Y dimensions along an path.</li>
139 * </ul>
140 */
141
142public class AnimatedVectorDrawableCompat extends VectorDrawableCommon
143        implements Animatable2Compat {
144    private static final String LOGTAG = "AnimatedVDCompat";
145
146    private static final String ANIMATED_VECTOR = "animated-vector";
147    private static final String TARGET = "target";
148
149    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
150
151    private AnimatedVectorDrawableCompatState mAnimatedVectorState;
152
153    private Context mContext;
154
155    private ArgbEvaluator mArgbEvaluator = null;
156
157    AnimatedVectorDrawableDelegateState mCachedConstantStateDelegate;
158
159    // Use internal listener to support AVDC's callback.
160    private Animator.AnimatorListener mAnimatorListener = null;
161
162    // Use an array to keep track of multiple call back associated with one drawable.
163    private ArrayList<Animatable2Compat.AnimationCallback> mAnimationCallbacks = null;
164
165
166    AnimatedVectorDrawableCompat() {
167        this(null, null, null);
168    }
169
170    private AnimatedVectorDrawableCompat(@Nullable Context context) {
171        this(context, null, null);
172    }
173
174    private AnimatedVectorDrawableCompat(@Nullable Context context,
175            @Nullable AnimatedVectorDrawableCompatState state,
176            @Nullable Resources res) {
177        mContext = context;
178        if (state != null) {
179            mAnimatedVectorState = state;
180        } else {
181            mAnimatedVectorState = new AnimatedVectorDrawableCompatState(context, state, mCallback,
182                    res);
183        }
184    }
185
186    /**
187     * mutate() will be effective only if the getConstantState() is returning non-null.
188     * Otherwise, it just return the current object without modification.
189     */
190    @Override
191    public Drawable mutate() {
192        if (mDelegateDrawable != null) {
193            mDelegateDrawable.mutate();
194        }
195        // For older platforms that there is no delegated drawable, we just return this without
196        // any modification here, and the getConstantState() will return null in this case.
197        return this;
198    }
199
200
201    /**
202     * Create a AnimatedVectorDrawableCompat object.
203     *
204     * @param context the context for creating the animators.
205     * @param resId   the resource ID for AnimatedVectorDrawableCompat object.
206     * @return a new AnimatedVectorDrawableCompat or null if parsing error is found.
207     */
208    @Nullable
209    public static AnimatedVectorDrawableCompat create(@NonNull Context context,
210            @DrawableRes int resId) {
211        if (Build.VERSION.SDK_INT >= 24) {
212            final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
213            drawable.mDelegateDrawable = ResourcesCompat.getDrawable(context.getResources(), resId,
214                    context.getTheme());
215            drawable.mDelegateDrawable.setCallback(drawable.mCallback);
216            drawable.mCachedConstantStateDelegate = new AnimatedVectorDrawableDelegateState(
217                    drawable.mDelegateDrawable.getConstantState());
218            return drawable;
219        }
220        Resources resources = context.getResources();
221        try {
222            //noinspection AndroidLintResourceType - Parse drawable as XML.
223            final XmlPullParser parser = resources.getXml(resId);
224            final AttributeSet attrs = Xml.asAttributeSet(parser);
225            int type;
226            while ((type = parser.next()) != XmlPullParser.START_TAG
227                    && type != XmlPullParser.END_DOCUMENT) {
228                // Empty loop
229            }
230            if (type != XmlPullParser.START_TAG) {
231                throw new XmlPullParserException("No start tag found");
232            }
233            return createFromXmlInner(context, context.getResources(), parser, attrs,
234                    context.getTheme());
235        } catch (XmlPullParserException e) {
236            Log.e(LOGTAG, "parser error", e);
237        } catch (IOException e) {
238            Log.e(LOGTAG, "parser error", e);
239        }
240        return null;
241    }
242
243    /**
244     * Create a AnimatedVectorDrawableCompat from inside an XML document using an optional
245     * {@link Theme}. Called on a parser positioned at a tag in an XML
246     * document, tries to create a Drawable from that tag. Returns {@code null}
247     * if the tag is not a valid drawable.
248     */
249    public static AnimatedVectorDrawableCompat createFromXmlInner(Context context, Resources r,
250            XmlPullParser parser, AttributeSet attrs, Theme theme)
251            throws XmlPullParserException, IOException {
252        final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
253        drawable.inflate(r, parser, attrs, theme);
254        return drawable;
255    }
256
257    /**
258     * {@inheritDoc}
259     * <strong>Note</strong> that we don't support constant state when SDK < 24.
260     * Make sure you check the return value before using it.
261     */
262    @Override
263    public ConstantState getConstantState() {
264        if (mDelegateDrawable != null && Build.VERSION.SDK_INT >= 24) {
265            return new AnimatedVectorDrawableDelegateState(mDelegateDrawable.getConstantState());
266        }
267        // We can't support constant state in older platform.
268        // We need Context to create the animator, and we can't save the context in the constant
269        // state.
270        return null;
271    }
272
273    @Override
274    public int getChangingConfigurations() {
275        if (mDelegateDrawable != null) {
276            return mDelegateDrawable.getChangingConfigurations();
277        }
278        return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations;
279    }
280
281    @Override
282    public void draw(Canvas canvas) {
283        if (mDelegateDrawable != null) {
284            mDelegateDrawable.draw(canvas);
285            return;
286        }
287        mAnimatedVectorState.mVectorDrawable.draw(canvas);
288        if (mAnimatedVectorState.mAnimatorSet.isStarted()) {
289            invalidateSelf();
290        }
291    }
292
293    @Override
294    protected void onBoundsChange(Rect bounds) {
295        if (mDelegateDrawable != null) {
296            mDelegateDrawable.setBounds(bounds);
297            return;
298        }
299        mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
300    }
301
302    @Override
303    protected boolean onStateChange(int[] state) {
304        if (mDelegateDrawable != null) {
305            return mDelegateDrawable.setState(state);
306        }
307        return mAnimatedVectorState.mVectorDrawable.setState(state);
308    }
309
310    @Override
311    protected boolean onLevelChange(int level) {
312        if (mDelegateDrawable != null) {
313            return mDelegateDrawable.setLevel(level);
314        }
315        return mAnimatedVectorState.mVectorDrawable.setLevel(level);
316    }
317
318    @Override
319    public int getAlpha() {
320        if (mDelegateDrawable != null) {
321            return DrawableCompat.getAlpha(mDelegateDrawable);
322        }
323        return mAnimatedVectorState.mVectorDrawable.getAlpha();
324    }
325
326    @Override
327    public void setAlpha(int alpha) {
328        if (mDelegateDrawable != null) {
329            mDelegateDrawable.setAlpha(alpha);
330            return;
331        }
332        mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
333    }
334
335    @Override
336    public void setColorFilter(ColorFilter colorFilter) {
337        if (mDelegateDrawable != null) {
338            mDelegateDrawable.setColorFilter(colorFilter);
339            return;
340        }
341        mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
342    }
343
344    @Override
345    public void setTint(int tint) {
346        if (mDelegateDrawable != null) {
347            DrawableCompat.setTint(mDelegateDrawable, tint);
348            return;
349        }
350
351        mAnimatedVectorState.mVectorDrawable.setTint(tint);
352    }
353
354    @Override
355    public void setTintList(ColorStateList tint) {
356        if (mDelegateDrawable != null) {
357            DrawableCompat.setTintList(mDelegateDrawable, tint);
358            return;
359        }
360
361        mAnimatedVectorState.mVectorDrawable.setTintList(tint);
362    }
363
364    @Override
365    public void setTintMode(PorterDuff.Mode tintMode) {
366        if (mDelegateDrawable != null) {
367            DrawableCompat.setTintMode(mDelegateDrawable, tintMode);
368            return;
369        }
370
371        mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
372    }
373
374    @Override
375    public boolean setVisible(boolean visible, boolean restart) {
376        if (mDelegateDrawable != null) {
377            return mDelegateDrawable.setVisible(visible, restart);
378        }
379        mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
380        return super.setVisible(visible, restart);
381    }
382
383    @Override
384    public boolean isStateful() {
385        if (mDelegateDrawable != null) {
386            return mDelegateDrawable.isStateful();
387        }
388        return mAnimatedVectorState.mVectorDrawable.isStateful();
389    }
390
391    @Override
392    public int getOpacity() {
393        if (mDelegateDrawable != null) {
394            return mDelegateDrawable.getOpacity();
395        }
396        return mAnimatedVectorState.mVectorDrawable.getOpacity();
397    }
398
399    @Override
400    public int getIntrinsicWidth() {
401        if (mDelegateDrawable != null) {
402            return mDelegateDrawable.getIntrinsicWidth();
403        }
404        return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
405    }
406
407    @Override
408    public int getIntrinsicHeight() {
409        if (mDelegateDrawable != null) {
410            return mDelegateDrawable.getIntrinsicHeight();
411        }
412        return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
413    }
414
415    @Override
416    public boolean isAutoMirrored() {
417        if (mDelegateDrawable != null) {
418            return DrawableCompat.isAutoMirrored(mDelegateDrawable);
419        }
420        return mAnimatedVectorState.mVectorDrawable.isAutoMirrored();
421    }
422
423    @Override
424    public void setAutoMirrored(boolean mirrored) {
425        if (mDelegateDrawable != null) {
426            DrawableCompat.setAutoMirrored(mDelegateDrawable, mirrored);
427            return;
428        }
429        mAnimatedVectorState.mVectorDrawable.setAutoMirrored(mirrored);
430    }
431
432    @Override
433    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
434            throws XmlPullParserException, IOException {
435        if (mDelegateDrawable != null) {
436            DrawableCompat.inflate(mDelegateDrawable, res, parser, attrs, theme);
437            return;
438        }
439        int eventType = parser.getEventType();
440        final int innerDepth = parser.getDepth() + 1;
441
442        // Parse everything until the end of the animated-vector element.
443        while (eventType != XmlPullParser.END_DOCUMENT
444                && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
445            if (eventType == XmlPullParser.START_TAG) {
446                final String tagName = parser.getName();
447                if (DBG_ANIMATION_VECTOR_DRAWABLE) {
448                    Log.v(LOGTAG, "tagName is " + tagName);
449                }
450                if (ANIMATED_VECTOR.equals(tagName)) {
451                    final TypedArray a =
452                            TypedArrayUtils.obtainAttributes(res, theme, attrs,
453                                    AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE);
454
455                    int drawableRes = a.getResourceId(
456                            AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_DRAWABLE, 0);
457                    if (DBG_ANIMATION_VECTOR_DRAWABLE) {
458                        Log.v(LOGTAG, "drawableRes is " + drawableRes);
459                    }
460                    if (drawableRes != 0) {
461                        VectorDrawableCompat vectorDrawable = VectorDrawableCompat.create(res,
462                                drawableRes, theme);
463                        vectorDrawable.setAllowCaching(false);
464                        vectorDrawable.setCallback(mCallback);
465                        if (mAnimatedVectorState.mVectorDrawable != null) {
466                            mAnimatedVectorState.mVectorDrawable.setCallback(null);
467                        }
468                        mAnimatedVectorState.mVectorDrawable = vectorDrawable;
469                    }
470                    a.recycle();
471                } else if (TARGET.equals(tagName)) {
472                    final TypedArray a =
473                            res.obtainAttributes(attrs,
474                                    AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET);
475                    final String target = a.getString(
476                            AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET_NAME);
477
478                    int id = a.getResourceId(
479                            AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET_ANIMATION,
480                            0);
481                    if (id != 0) {
482                        if (mContext != null) {
483                            // There are some important features (like path morphing), added into
484                            // Animator code to support AVD at API 21.
485                            Animator objectAnimator = AnimatorInflaterCompat.loadAnimator(
486                                    mContext, id);
487                            setupAnimatorsForTarget(target, objectAnimator);
488                        } else {
489                            a.recycle();
490                            throw new IllegalStateException("Context can't be null when inflating" +
491                                    " animators");
492                        }
493                    }
494                    a.recycle();
495                }
496            }
497            eventType = parser.next();
498        }
499
500        mAnimatedVectorState.setupAnimatorSet();
501    }
502
503    @Override
504    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs)
505            throws XmlPullParserException, IOException {
506        inflate(res, parser, attrs, null);
507    }
508
509    @Override
510    public void applyTheme(Theme t) {
511        if (mDelegateDrawable != null) {
512            DrawableCompat.applyTheme(mDelegateDrawable, t);
513            return;
514        }
515        // TODO: support theming in older platform.
516        return;
517    }
518
519    @Override
520    public boolean canApplyTheme() {
521        if (mDelegateDrawable != null) {
522            return DrawableCompat.canApplyTheme(mDelegateDrawable);
523        }
524        // TODO: support theming in older platform.
525        return false;
526    }
527
528    /**
529     * Constant state for delegating the creating drawable job.
530     * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains
531     * a delegated VectorDrawable instance.
532     */
533    @RequiresApi(24)
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            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
691            return ((AnimatedVectorDrawable) mDelegateDrawable).isRunning();
692        }
693        return mAnimatedVectorState.mAnimatorSet.isRunning();
694    }
695
696    @Override
697    public void start() {
698        if (mDelegateDrawable != null) {
699            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
700            ((AnimatedVectorDrawable) mDelegateDrawable).start();
701            return;
702        }
703        // If any one of the animator has not ended, do nothing.
704        if (mAnimatedVectorState.mAnimatorSet.isStarted()) {
705            return;
706        }
707        // Otherwise, kick off animatorSet.
708        mAnimatedVectorState.mAnimatorSet.start();
709        invalidateSelf();
710    }
711
712    @Override
713    public void stop() {
714        if (mDelegateDrawable != null) {
715            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
716            ((AnimatedVectorDrawable) mDelegateDrawable).stop();
717            return;
718        }
719        mAnimatedVectorState.mAnimatorSet.end();
720    }
721
722    final Callback mCallback = new Callback() {
723        @Override
724        public void invalidateDrawable(Drawable who) {
725            invalidateSelf();
726        }
727
728        @Override
729        public void scheduleDrawable(Drawable who, Runnable what, long when) {
730            scheduleSelf(what, when);
731        }
732
733        @Override
734        public void unscheduleDrawable(Drawable who, Runnable what) {
735            unscheduleSelf(what);
736        }
737    };
738
739    /**
740     * A helper function to unregister the Animatable2Compat callback from the platform's
741     * Animatable2 callback, while keeping the internal array of callback up to date.
742     */
743    @RequiresApi(23)
744    private static boolean unregisterPlatformCallback(AnimatedVectorDrawable dr,
745            Animatable2Compat.AnimationCallback callback) {
746        return dr.unregisterAnimationCallback(callback.getPlatformCallback());
747    }
748
749    @Override
750    public void registerAnimationCallback(@NonNull Animatable2Compat.AnimationCallback
751            callback) {
752        if (mDelegateDrawable != null) {
753            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
754            registerPlatformCallback((AnimatedVectorDrawable) mDelegateDrawable, callback);
755            return;
756        }
757
758        if (callback == null) {
759            return;
760        }
761
762        // Add listener accordingly.
763        if (mAnimationCallbacks == null) {
764            mAnimationCallbacks = new ArrayList<>();
765        }
766
767        if (mAnimationCallbacks.contains(callback)) {
768            // If this call back is already in, then don't need to append another copy.
769            return;
770        }
771
772        mAnimationCallbacks.add(callback);
773
774        if (mAnimatorListener == null) {
775            // Create a animator listener and trigger the callback events when listener is
776            // triggered.
777            mAnimatorListener = new AnimatorListenerAdapter() {
778                @Override
779                public void onAnimationStart(Animator animation) {
780                    ArrayList<Animatable2Compat.AnimationCallback> tmpCallbacks =
781                            new ArrayList<>(mAnimationCallbacks);
782                    int size = tmpCallbacks.size();
783                    for (int i = 0; i < size; i++) {
784                        tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawableCompat.this);
785                    }
786                }
787
788                @Override
789                public void onAnimationEnd(Animator animation) {
790                    ArrayList<Animatable2Compat.AnimationCallback> tmpCallbacks =
791                            new ArrayList<>(mAnimationCallbacks);
792                    int size = tmpCallbacks.size();
793                    for (int i = 0; i < size; i++) {
794                        tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawableCompat.this);
795                    }
796                }
797            };
798        }
799        mAnimatedVectorState.mAnimatorSet.addListener(mAnimatorListener);
800    }
801
802    /**
803     * A helper function to register the Animatable2Compat callback on the platform's Animatable2
804     * callback.
805     */
806    @RequiresApi(23)
807    private static void registerPlatformCallback(@NonNull AnimatedVectorDrawable avd,
808            @NonNull final Animatable2Compat.AnimationCallback callback) {
809        avd.registerAnimationCallback(callback.getPlatformCallback());
810    }
811
812    /**
813     * A helper function to clean up the animator listener in the mAnimatorSet.
814     */
815    private void removeAnimatorSetListener() {
816        if (mAnimatorListener != null) {
817            mAnimatedVectorState.mAnimatorSet.removeListener(mAnimatorListener);
818            mAnimatorListener = null;
819        }
820    }
821
822    @Override
823    public boolean unregisterAnimationCallback(
824            @NonNull Animatable2Compat.AnimationCallback callback) {
825        if (mDelegateDrawable != null) {
826            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
827            unregisterPlatformCallback((AnimatedVectorDrawable) mDelegateDrawable, callback);
828        }
829
830        if (mAnimationCallbacks == null || callback == null) {
831            // Nothing to be removed.
832            return false;
833        }
834        boolean removed = mAnimationCallbacks.remove(callback);
835
836        //  When the last call back unregistered, remove the listener accordingly.
837        if (mAnimationCallbacks.size() == 0) {
838            removeAnimatorSetListener();
839        }
840        return removed;
841    }
842
843    @Override
844    public void clearAnimationCallbacks() {
845        if (mDelegateDrawable != null) {
846            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
847            ((AnimatedVectorDrawable) mDelegateDrawable).clearAnimationCallbacks();
848            return;
849        }
850        removeAnimatorSetListener();
851        if (mAnimationCallbacks == null) {
852            return;
853        }
854
855        mAnimationCallbacks.clear();
856    }
857
858    /**
859     * Utility function to register callback to Drawable, when the drawable is created from XML and
860     * referred in Java code, e.g: ImageView.getDrawable().
861     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
862     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
863     */
864    public static void registerAnimationCallback(Drawable dr,
865            Animatable2Compat.AnimationCallback callback) {
866        if (dr == null || callback == null) {
867            return;
868        }
869        if (!(dr instanceof Animatable)) {
870            return;
871        }
872
873        if (Build.VERSION.SDK_INT >= 24) {
874            registerPlatformCallback((AnimatedVectorDrawable) dr, callback);
875        } else {
876            ((AnimatedVectorDrawableCompat) dr).registerAnimationCallback(callback);
877        }
878    }
879
880    /**
881     * Utility function to unregister animation callback from Drawable, when the drawable is
882     * created from XML and referred in Java code, e.g: ImageView.getDrawable().
883     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
884     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
885     */
886    public static boolean unregisterAnimationCallback(Drawable dr,
887            Animatable2Compat.AnimationCallback callback) {
888        if (dr == null || callback == null) {
889            return false;
890        }
891        if (!(dr instanceof Animatable)) {
892            return false;
893        }
894
895        if (Build.VERSION.SDK_INT >= 24) {
896            return unregisterPlatformCallback((AnimatedVectorDrawable) dr, callback);
897        } else {
898            return ((AnimatedVectorDrawableCompat) dr).unregisterAnimationCallback(callback);
899        }
900    }
901
902    /**
903     * Utility function to clear animation callbacks from Drawable, when the drawable is
904     * created from XML and referred in Java code, e.g: ImageView.getDrawable().
905     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
906     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
907     */
908    public static void clearAnimationCallbacks(Drawable dr) {
909        if (dr == null || !(dr instanceof Animatable)) {
910            return;
911        }
912        if (Build.VERSION.SDK_INT >= 24) {
913            ((AnimatedVectorDrawable) dr).clearAnimationCallbacks();
914        } else {
915            ((AnimatedVectorDrawableCompat) dr).clearAnimationCallbacks();
916        }
917
918    }
919}
920