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