AnimatedVectorDrawableCompat.java revision b6086751979cb14740815502597e9fcfddb7054a
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.annotation.SuppressLint; 23import android.content.Context; 24import android.content.res.ColorStateList; 25import android.content.res.Resources; 26import android.content.res.Resources.Theme; 27import android.content.res.TypedArray; 28import android.graphics.Canvas; 29import android.graphics.ColorFilter; 30import android.graphics.PorterDuff; 31import android.graphics.Rect; 32import android.graphics.drawable.Animatable; 33import android.graphics.drawable.AnimatedVectorDrawable; 34import android.graphics.drawable.Drawable; 35import android.os.Build; 36import android.support.annotation.DrawableRes; 37import android.support.annotation.NonNull; 38import android.support.annotation.Nullable; 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 * </ul> 135 * <p/> 136 * But not support this one feature yet: 137 * <ul> 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 143@SuppressLint("NewApi") 144public class AnimatedVectorDrawableCompat extends VectorDrawableCommon 145 implements Animatable2Compat { 146 private static final String LOGTAG = "AnimatedVDCompat"; 147 148 private static final String ANIMATED_VECTOR = "animated-vector"; 149 private static final String TARGET = "target"; 150 151 private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; 152 153 private AnimatedVectorDrawableCompatState mAnimatedVectorState; 154 155 private Context mContext; 156 157 private ArgbEvaluator mArgbEvaluator = null; 158 159 AnimatedVectorDrawableDelegateState mCachedConstantStateDelegate; 160 161 // Use internal listener to support AVDC's callback. 162 private Animator.AnimatorListener mAnimatorListener = null; 163 164 // Use an array to keep track of multiple call back associated with one drawable. 165 private ArrayList<Animatable2Compat.AnimationCallback> mAnimationCallbacks = null; 166 167 168 AnimatedVectorDrawableCompat() { 169 this(null, null, null); 170 } 171 172 private AnimatedVectorDrawableCompat(@Nullable Context context) { 173 this(context, null, null); 174 } 175 176 private AnimatedVectorDrawableCompat(@Nullable Context context, 177 @Nullable AnimatedVectorDrawableCompatState state, 178 @Nullable Resources res) { 179 mContext = context; 180 if (state != null) { 181 mAnimatedVectorState = state; 182 } else { 183 mAnimatedVectorState = new AnimatedVectorDrawableCompatState(context, state, mCallback, 184 res); 185 } 186 } 187 188 /** 189 * mutate() will be effective only if the getConstantState() is returning non-null. 190 * Otherwise, it just return the current object without modification. 191 */ 192 @Override 193 public Drawable mutate() { 194 if (mDelegateDrawable != null) { 195 mDelegateDrawable.mutate(); 196 } 197 // For older platforms that there is no delegated drawable, we just return this without 198 // any modification here, and the getConstantState() will return null in this case. 199 return this; 200 } 201 202 203 /** 204 * Create a AnimatedVectorDrawableCompat object. 205 * 206 * @param context the context for creating the animators. 207 * @param resId the resource ID for AnimatedVectorDrawableCompat object. 208 * @return a new AnimatedVectorDrawableCompat or null if parsing error is found. 209 */ 210 @Nullable 211 public static AnimatedVectorDrawableCompat create(@NonNull Context context, 212 @DrawableRes int resId) { 213 if (Build.VERSION.SDK_INT >= 24) { 214 final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context); 215 drawable.mDelegateDrawable = ResourcesCompat.getDrawable(context.getResources(), resId, 216 context.getTheme()); 217 drawable.mDelegateDrawable.setCallback(drawable.mCallback); 218 drawable.mCachedConstantStateDelegate = new AnimatedVectorDrawableDelegateState( 219 drawable.mDelegateDrawable.getConstantState()); 220 return drawable; 221 } 222 Resources resources = context.getResources(); 223 try { 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) { 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 mDelegateDrawable.setAutoMirrored(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 private static class AnimatedVectorDrawableDelegateState extends ConstantState { 535 private final ConstantState mDelegateState; 536 537 public AnimatedVectorDrawableDelegateState(ConstantState state) { 538 mDelegateState = state; 539 } 540 541 @Override 542 public Drawable newDrawable() { 543 AnimatedVectorDrawableCompat drawableCompat = 544 new AnimatedVectorDrawableCompat(); 545 drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(); 546 drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback); 547 return drawableCompat; 548 } 549 550 @Override 551 public Drawable newDrawable(Resources res) { 552 AnimatedVectorDrawableCompat drawableCompat = 553 new AnimatedVectorDrawableCompat(); 554 drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res); 555 drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback); 556 return drawableCompat; 557 } 558 559 @Override 560 public Drawable newDrawable(Resources res, Theme theme) { 561 AnimatedVectorDrawableCompat drawableCompat = 562 new AnimatedVectorDrawableCompat(); 563 drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res, theme); 564 drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback); 565 return drawableCompat; 566 } 567 568 @Override 569 public boolean canApplyTheme() { 570 return mDelegateState.canApplyTheme(); 571 } 572 573 @Override 574 public int getChangingConfigurations() { 575 return mDelegateState.getChangingConfigurations(); 576 } 577 } 578 579 private static class AnimatedVectorDrawableCompatState extends ConstantState { 580 int mChangingConfigurations; 581 VectorDrawableCompat mVectorDrawable; 582 // Combining the array of Animators into a single AnimatorSet to hook up listener easier. 583 AnimatorSet mAnimatorSet; 584 private ArrayList<Animator> mAnimators; 585 ArrayMap<Animator, String> mTargetNameMap; 586 587 public AnimatedVectorDrawableCompatState(Context context, 588 AnimatedVectorDrawableCompatState copy, Callback owner, Resources res) { 589 if (copy != null) { 590 mChangingConfigurations = copy.mChangingConfigurations; 591 if (copy.mVectorDrawable != null) { 592 final ConstantState cs = copy.mVectorDrawable.getConstantState(); 593 if (res != null) { 594 mVectorDrawable = (VectorDrawableCompat) cs.newDrawable(res); 595 } else { 596 mVectorDrawable = (VectorDrawableCompat) cs.newDrawable(); 597 } 598 mVectorDrawable = (VectorDrawableCompat) mVectorDrawable.mutate(); 599 mVectorDrawable.setCallback(owner); 600 mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds()); 601 mVectorDrawable.setAllowCaching(false); 602 } 603 if (copy.mAnimators != null) { 604 final int numAnimators = copy.mAnimators.size(); 605 mAnimators = new ArrayList<Animator>(numAnimators); 606 mTargetNameMap = new ArrayMap<Animator, String>(numAnimators); 607 for (int i = 0; i < numAnimators; ++i) { 608 Animator anim = copy.mAnimators.get(i); 609 Animator animClone = anim.clone(); 610 String targetName = copy.mTargetNameMap.get(anim); 611 Object targetObject = mVectorDrawable.getTargetByName(targetName); 612 animClone.setTarget(targetObject); 613 mAnimators.add(animClone); 614 mTargetNameMap.put(animClone, targetName); 615 } 616 setupAnimatorSet(); 617 } 618 } 619 } 620 621 @Override 622 public Drawable newDrawable() { 623 throw new IllegalStateException("No constant state support for SDK < 24."); 624 } 625 626 @Override 627 public Drawable newDrawable(Resources res) { 628 throw new IllegalStateException("No constant state support for SDK < 24."); 629 } 630 631 @Override 632 public int getChangingConfigurations() { 633 return mChangingConfigurations; 634 } 635 636 public void setupAnimatorSet() { 637 if (mAnimatorSet == null) { 638 mAnimatorSet = new AnimatorSet(); 639 } 640 mAnimatorSet.playTogether(mAnimators); 641 } 642 } 643 644 /** 645 * Utility function to fix color interpolation prior to Lollipop. Without this fix, colors 646 * are evaluated as raw integers instead of as colors, which leads to artifacts during 647 * fillColor animations. 648 */ 649 private void setupColorAnimator(Animator animator) { 650 if (animator instanceof AnimatorSet) { 651 List<Animator> childAnimators = ((AnimatorSet) animator).getChildAnimations(); 652 if (childAnimators != null) { 653 for (int i = 0; i < childAnimators.size(); ++i) { 654 setupColorAnimator(childAnimators.get(i)); 655 } 656 } 657 } 658 if (animator instanceof ObjectAnimator) { 659 ObjectAnimator objectAnim = (ObjectAnimator) animator; 660 final String propertyName = objectAnim.getPropertyName(); 661 if ("fillColor".equals(propertyName) || "strokeColor".equals(propertyName)) { 662 if (mArgbEvaluator == null) { 663 mArgbEvaluator = new ArgbEvaluator(); 664 } 665 objectAnim.setEvaluator(mArgbEvaluator); 666 } 667 } 668 } 669 670 private void setupAnimatorsForTarget(String name, Animator animator) { 671 Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name); 672 animator.setTarget(target); 673 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 674 setupColorAnimator(animator); 675 } 676 if (mAnimatedVectorState.mAnimators == null) { 677 mAnimatedVectorState.mAnimators = new ArrayList<Animator>(); 678 mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>(); 679 } 680 mAnimatedVectorState.mAnimators.add(animator); 681 mAnimatedVectorState.mTargetNameMap.put(animator, name); 682 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 683 Log.v(LOGTAG, "add animator for target " + name + " " + animator); 684 } 685 } 686 687 @Override 688 public boolean isRunning() { 689 if (mDelegateDrawable != null) { 690 return ((AnimatedVectorDrawable) mDelegateDrawable).isRunning(); 691 } 692 return mAnimatedVectorState.mAnimatorSet.isRunning(); 693 } 694 695 @Override 696 public void start() { 697 if (mDelegateDrawable != null) { 698 ((AnimatedVectorDrawable) mDelegateDrawable).start(); 699 return; 700 } 701 // If any one of the animator has not ended, do nothing. 702 if (mAnimatedVectorState.mAnimatorSet.isStarted()) { 703 return; 704 } 705 // Otherwise, kick off animatorSet. 706 mAnimatedVectorState.mAnimatorSet.start(); 707 invalidateSelf(); 708 } 709 710 @Override 711 public void stop() { 712 if (mDelegateDrawable != 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 private static boolean unregisterPlatformCallback(AnimatedVectorDrawable dr, 741 Animatable2Compat.AnimationCallback callback) { 742 return dr.unregisterAnimationCallback(callback.getPlatformCallback()); 743 } 744 745 @Override 746 public void registerAnimationCallback(@NonNull Animatable2Compat.AnimationCallback 747 callback) { 748 if (mDelegateDrawable != null) { 749 registerPlatformCallback((AnimatedVectorDrawable) mDelegateDrawable, callback); 750 return; 751 } 752 753 if (callback == null) { 754 return; 755 } 756 757 // Add listener accordingly. 758 if (mAnimationCallbacks == null) { 759 mAnimationCallbacks = new ArrayList<>(); 760 } 761 762 if (mAnimationCallbacks.contains(callback)) { 763 // If this call back is already in, then don't need to append another copy. 764 return; 765 } 766 767 mAnimationCallbacks.add(callback); 768 769 if (mAnimatorListener == null) { 770 // Create a animator listener and trigger the callback events when listener is 771 // triggered. 772 mAnimatorListener = new AnimatorListenerAdapter() { 773 @Override 774 public void onAnimationStart(Animator animation) { 775 ArrayList<Animatable2Compat.AnimationCallback> tmpCallbacks = 776 new ArrayList<>(mAnimationCallbacks); 777 int size = tmpCallbacks.size(); 778 for (int i = 0; i < size; i++) { 779 tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawableCompat.this); 780 } 781 } 782 783 @Override 784 public void onAnimationEnd(Animator animation) { 785 ArrayList<Animatable2Compat.AnimationCallback> tmpCallbacks = 786 new ArrayList<>(mAnimationCallbacks); 787 int size = tmpCallbacks.size(); 788 for (int i = 0; i < size; i++) { 789 tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawableCompat.this); 790 } 791 } 792 }; 793 } 794 mAnimatedVectorState.mAnimatorSet.addListener(mAnimatorListener); 795 } 796 797 /** 798 * A helper function to register the Animatable2Compat callback on the platform's Animatable2 799 * callback. 800 */ 801 private static void registerPlatformCallback(@NonNull AnimatedVectorDrawable avd, 802 @NonNull final Animatable2Compat.AnimationCallback callback) { 803 avd.registerAnimationCallback(callback.getPlatformCallback()); 804 } 805 806 /** 807 * A helper function to clean up the animator listener in the mAnimatorSet. 808 */ 809 private void removeAnimatorSetListener() { 810 if (mAnimatorListener != null) { 811 mAnimatedVectorState.mAnimatorSet.removeListener(mAnimatorListener); 812 mAnimatorListener = null; 813 } 814 } 815 816 @Override 817 public boolean unregisterAnimationCallback( 818 @NonNull Animatable2Compat.AnimationCallback callback) { 819 if (mDelegateDrawable != null) { 820 unregisterPlatformCallback((AnimatedVectorDrawable) mDelegateDrawable, callback); 821 } 822 823 if (mAnimationCallbacks == null || callback == null) { 824 // Nothing to be removed. 825 return false; 826 } 827 boolean removed = mAnimationCallbacks.remove(callback); 828 829 // When the last call back unregistered, remove the listener accordingly. 830 if (mAnimationCallbacks.size() == 0) { 831 removeAnimatorSetListener(); 832 } 833 return removed; 834 } 835 836 @Override 837 public void clearAnimationCallbacks() { 838 if (mDelegateDrawable != null) { 839 ((AnimatedVectorDrawable) mDelegateDrawable).clearAnimationCallbacks(); 840 return; 841 } 842 removeAnimatorSetListener(); 843 if (mAnimationCallbacks == null) { 844 return; 845 } 846 847 mAnimationCallbacks.clear(); 848 } 849 850 /** 851 * Utility function to register callback to Drawable, when the drawable is created from XML and 852 * referred in Java code, e.g: ImageView.getDrawable(). 853 * From API 24 on, the drawable is treated as an AnimatedVectorDrawable. 854 * Otherwise, it is treated as AnimatedVectorDrawableCompat. 855 */ 856 public static void registerAnimationCallback(Drawable dr, 857 Animatable2Compat.AnimationCallback callback) { 858 if (dr == null || callback == null) { 859 return; 860 } 861 if (!(dr instanceof Animatable)) { 862 return; 863 } 864 865 if (Build.VERSION.SDK_INT >= 24) { 866 registerPlatformCallback((AnimatedVectorDrawable) dr, callback); 867 } else { 868 ((AnimatedVectorDrawableCompat) dr).registerAnimationCallback(callback); 869 } 870 } 871 872 /** 873 * Utility function to unregister animation callback from Drawable, when the drawable is 874 * created from XML and referred in Java code, e.g: ImageView.getDrawable(). 875 * From API 24 on, the drawable is treated as an AnimatedVectorDrawable. 876 * Otherwise, it is treated as AnimatedVectorDrawableCompat. 877 */ 878 public static boolean unregisterAnimationCallback(Drawable dr, 879 Animatable2Compat.AnimationCallback callback) { 880 if (dr == null || callback == null) { 881 return false; 882 } 883 if (!(dr instanceof Animatable)) { 884 return false; 885 } 886 887 if (Build.VERSION.SDK_INT >= 24) { 888 return unregisterPlatformCallback((AnimatedVectorDrawable) dr, callback); 889 } else { 890 return ((AnimatedVectorDrawableCompat) dr).unregisterAnimationCallback(callback); 891 } 892 } 893 894 /** 895 * Utility function to clear animation callbacks from Drawable, when the drawable is 896 * created from XML and referred in Java code, e.g: ImageView.getDrawable(). 897 * From API 24 on, the drawable is treated as an AnimatedVectorDrawable. 898 * Otherwise, it is treated as AnimatedVectorDrawableCompat. 899 */ 900 public static void clearAnimationCallbacks(Drawable dr) { 901 if (dr == null || !(dr instanceof Animatable)) { 902 return; 903 } 904 if (Build.VERSION.SDK_INT >= 24) { 905 ((AnimatedVectorDrawable) dr).clearAnimationCallbacks(); 906 } else { 907 ((AnimatedVectorDrawableCompat) dr).clearAnimationCallbacks(); 908 } 909 910 } 911} 912