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><vector></td> 80 * <td>alpha</td> 81 * </tr> 82 * <tr> 83 * <td rowspan="7"><group></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"><path></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