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