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