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 android.support.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.support.annotation.DrawableRes; 36import android.support.annotation.NonNull; 37import android.support.annotation.Nullable; 38import android.support.annotation.RequiresApi; 39import android.support.v4.content.res.ResourcesCompat; 40import android.support.v4.content.res.TypedArrayUtils; 41import android.support.v4.graphics.drawable.DrawableCompat; 42import android.support.v4.util.ArrayMap; 43import android.util.AttributeSet; 44import android.util.Log; 45import android.util.Xml; 46 47import org.xmlpull.v1.XmlPullParser; 48import org.xmlpull.v1.XmlPullParserException; 49 50import java.io.IOException; 51import java.util.ArrayList; 52import java.util.List; 53 54/** 55 * For API 24 and above, this class is delegating to the framework's {@link 56 * AnimatedVectorDrawable}. 57 * For older API version, this class uses {@link android.animation.ObjectAnimator} and 58 * {@link android.animation.AnimatorSet} to animate the properties of a 59 * {@link VectorDrawableCompat} to create an animated drawable. 60 * <p/> 61 * AnimatedVectorDrawableCompat are defined in the same XML format as 62 * {@link AnimatedVectorDrawable}. 63 * <p/> 64 * Here are all the animatable attributes in {@link VectorDrawableCompat}: 65 * <table border="2" align="center" cellpadding="5"> 66 * <thead> 67 * <tr> 68 * <th>Element Name</th> 69 * <th>Animatable attribute name</th> 70 * </tr> 71 * </thead> 72 * <tr> 73 * <td><vector></td> 74 * <td>alpha</td> 75 * </tr> 76 * <tr> 77 * <td rowspan="7"><group></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"><path></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 139public 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 163 AnimatedVectorDrawableCompat() { 164 this(null, null, null); 165 } 166 167 private AnimatedVectorDrawableCompat(@Nullable Context context) { 168 this(context, null, null); 169 } 170 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 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 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 */ 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 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 271 public int getChangingConfigurations() { 272 if (mDelegateDrawable != null) { 273 return mDelegateDrawable.getChangingConfigurations(); 274 } 275 return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations; 276 } 277 278 @Override 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 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 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 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 316 public int getAlpha() { 317 if (mDelegateDrawable != null) { 318 return DrawableCompat.getAlpha(mDelegateDrawable); 319 } 320 return mAnimatedVectorState.mVectorDrawable.getAlpha(); 321 } 322 323 @Override 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 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 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 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 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 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 381 public boolean isStateful() { 382 if (mDelegateDrawable != null) { 383 return mDelegateDrawable.isStateful(); 384 } 385 return mAnimatedVectorState.mVectorDrawable.isStateful(); 386 } 387 388 @Override 389 public int getOpacity() { 390 if (mDelegateDrawable != null) { 391 return mDelegateDrawable.getOpacity(); 392 } 393 return mAnimatedVectorState.mVectorDrawable.getOpacity(); 394 } 395 396 @Override 397 public int getIntrinsicWidth() { 398 if (mDelegateDrawable != null) { 399 return mDelegateDrawable.getIntrinsicWidth(); 400 } 401 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); 402 } 403 404 @Override 405 public int getIntrinsicHeight() { 406 if (mDelegateDrawable != null) { 407 return mDelegateDrawable.getIntrinsicHeight(); 408 } 409 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); 410 } 411 412 @Override 413 public boolean isAutoMirrored() { 414 if (mDelegateDrawable != null) { 415 return DrawableCompat.isAutoMirrored(mDelegateDrawable); 416 } 417 return mAnimatedVectorState.mVectorDrawable.isAutoMirrored(); 418 } 419 420 @Override 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 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 501 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs) 502 throws XmlPullParserException, IOException { 503 inflate(res, parser, attrs, null); 504 } 505 506 @Override 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 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 534 public AnimatedVectorDrawableDelegateState(ConstantState state) { 535 mDelegateState = state; 536 } 537 538 @Override 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 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 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 566 public boolean canApplyTheme() { 567 return mDelegateState.canApplyTheme(); 568 } 569 570 @Override 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 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 619 public Drawable newDrawable() { 620 throw new IllegalStateException("No constant state support for SDK < 24."); 621 } 622 623 @Override 624 public Drawable newDrawable(Resources res) { 625 throw new IllegalStateException("No constant state support for SDK < 24."); 626 } 627 628 @Override 629 public int getChangingConfigurations() { 630 return mChangingConfigurations; 631 } 632 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 */ 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 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 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 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 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) 741 private static boolean unregisterPlatformCallback(AnimatedVectorDrawable dr, 742 Animatable2Compat.AnimationCallback callback) { 743 return dr.unregisterAnimationCallback(callback.getPlatformCallback()); 744 } 745 746 @Override 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) 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 */ 812 private void removeAnimatorSetListener() { 813 if (mAnimatorListener != null) { 814 mAnimatedVectorState.mAnimatorSet.removeListener(mAnimatorListener); 815 mAnimatorListener = null; 816 } 817 } 818 819 @Override 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 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 */ 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 */ 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 */ 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