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.AnimatorInflater; 19import android.animation.AnimatorSet; 20import android.animation.ArgbEvaluator; 21import android.animation.ObjectAnimator; 22import android.annotation.TargetApi; 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.graphics.drawable.DrawableCompat; 41import android.support.v4.util.ArrayMap; 42import android.util.AttributeSet; 43import android.util.Log; 44import android.util.Xml; 45 46import org.xmlpull.v1.XmlPullParser; 47import org.xmlpull.v1.XmlPullParserException; 48 49import java.io.IOException; 50import java.util.ArrayList; 51import java.util.List; 52 53/** 54 * For API 24 and above, this class is delegating to the framework's {@link AnimatedVectorDrawable}. 55 * For older API version, this class uses {@link android.animation.ObjectAnimator} and 56 * {@link android.animation.AnimatorSet} to animate the properties of a 57 * {@link VectorDrawableCompat} to create an animated drawable. 58 * <p/> 59 * AnimatedVectorDrawableCompat are defined in the same XML format as {@link AnimatedVectorDrawable}. 60 * <p/> 61 * Here are all the animatable attributes in {@link VectorDrawableCompat}: 62 * <table border="2" align="center" cellpadding="5"> 63 * <thead> 64 * <tr> 65 * <th>Element Name</th> 66 * <th>Animatable attribute name</th> 67 * </tr> 68 * </thead> 69 * <tr> 70 * <td><vector></td> 71 * <td>alpha</td> 72 * </tr> 73 * <tr> 74 * <td rowspan="7"><group></td> 75 * <td>rotation</td> 76 * </tr> 77 * <tr> 78 * <td>pivotX</td> 79 * </tr> 80 * <tr> 81 * <td>pivotY</td> 82 * </tr> 83 * <tr> 84 * <td>scaleX</td> 85 * </tr> 86 * <tr> 87 * <td>scaleY</td> 88 * </tr> 89 * <tr> 90 * <td>translateX</td> 91 * </tr> 92 * <tr> 93 * <td>translateY</td> 94 * </tr> 95 * <tr> 96 * <td rowspan="8"><path></td> 97 * <td>fillColor</td> 98 * </tr> 99 * <tr> 100 * <td>strokeColor</td> 101 * </tr> 102 * <tr> 103 * <td>strokeWidth</td> 104 * </tr> 105 * <tr> 106 * <td>strokeAlpha</td> 107 * </tr> 108 * <tr> 109 * <td>fillAlpha</td> 110 * </tr> 111 * <tr> 112 * <td>trimPathStart</td> 113 * </tr> 114 * <tr> 115 * <td>trimPathOffset</td> 116 * </tr> 117 * </table> 118 * <p/> 119 * You can always create a AnimatedVectorDrawableCompat object and use it as a Drawable by the Java 120 * API. In order to refer to AnimatedVectorDrawableCompat inside a XML file, you can use 121 * app:srcCompat attribute in AppCompat library's ImageButton or ImageView. 122 * <p/> 123 * Note that the animation in AnimatedVectorDrawableCompat has to be valid and functional based on 124 * the SDK version the app will be running on. Before SDK version 21, the animation system didn't 125 * support the following features: 126 * <ul> 127 * <li>Path Morphing (PathType evaluator). This is used for morphing one path into another.</li> 128 * <li>Path Interpolation. This is used to defined a flexible interpolator (represented as a path) 129 * instead of the system defined ones like LinearInterpolator.</li> 130 * <li>Animating 2 values in one ObjectAnimator according to one path's X value and Y value. One 131 * usage is moving one object in both X and Y dimensions along an path.</li> 132 * </ul> 133 */ 134@TargetApi(Build.VERSION_CODES.LOLLIPOP) 135public class AnimatedVectorDrawableCompat extends VectorDrawableCommon implements Animatable { 136 private static final String LOGTAG = "AnimatedVDCompat"; 137 138 private static final String ANIMATED_VECTOR = "animated-vector"; 139 private static final String TARGET = "target"; 140 141 private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; 142 143 private AnimatedVectorDrawableCompatState mAnimatedVectorState; 144 145 private Context mContext; 146 147 private ArgbEvaluator mArgbEvaluator = null; 148 149 AnimatedVectorDrawableDelegateState mCachedConstantStateDelegate; 150 151 AnimatedVectorDrawableCompat() { 152 this(null, null, null); 153 } 154 155 private AnimatedVectorDrawableCompat(@Nullable Context context) { 156 this(context, null, null); 157 } 158 159 private AnimatedVectorDrawableCompat(@Nullable Context context, 160 @Nullable AnimatedVectorDrawableCompatState state, 161 @Nullable Resources res) { 162 mContext = context; 163 if (state != null) { 164 mAnimatedVectorState = state; 165 } else { 166 mAnimatedVectorState = new AnimatedVectorDrawableCompatState(context, state, mCallback, 167 res); 168 } 169 } 170 171 /** 172 * mutate() will be effective only if the getConstantState() is returning non-null. 173 * Otherwise, it just return the current object without modification. 174 */ 175 @Override 176 public Drawable mutate() { 177 if (mDelegateDrawable != null) { 178 mDelegateDrawable.mutate(); 179 } 180 // For older platforms that there is no delegated drawable, we just return this without 181 // any modification here, and the getConstantState() will return null in this case. 182 return this; 183 } 184 185 186 /** 187 * Create a AnimatedVectorDrawableCompat object. 188 * 189 * @param context the context for creating the animators. 190 * @param resId the resource ID for AnimatedVectorDrawableCompat object. 191 * @return a new AnimatedVectorDrawableCompat or null if parsing error is found. 192 */ 193 @Nullable 194 public static AnimatedVectorDrawableCompat create(@NonNull Context context, 195 @DrawableRes int resId) { 196 if (Build.VERSION.SDK_INT >= 24) { 197 final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context); 198 drawable.mDelegateDrawable = ResourcesCompat.getDrawable(context.getResources(), resId, 199 context.getTheme()); 200 drawable.mDelegateDrawable.setCallback(drawable.mCallback); 201 drawable.mCachedConstantStateDelegate = new AnimatedVectorDrawableDelegateState( 202 drawable.mDelegateDrawable.getConstantState()); 203 return drawable; 204 } 205 Resources resources = context.getResources(); 206 try { 207 final XmlPullParser parser = resources.getXml(resId); 208 final AttributeSet attrs = Xml.asAttributeSet(parser); 209 int type; 210 while ((type = parser.next()) != XmlPullParser.START_TAG 211 && type != XmlPullParser.END_DOCUMENT) { 212 // Empty loop 213 } 214 if (type != XmlPullParser.START_TAG) { 215 throw new XmlPullParserException("No start tag found"); 216 } 217 return createFromXmlInner(context, context.getResources(), parser, attrs, 218 context.getTheme()); 219 } catch (XmlPullParserException e) { 220 Log.e(LOGTAG, "parser error", e); 221 } catch (IOException e) { 222 Log.e(LOGTAG, "parser error", e); 223 } 224 return null; 225 } 226 227 /** 228 * Create a AnimatedVectorDrawableCompat from inside an XML document using an optional 229 * {@link Theme}. Called on a parser positioned at a tag in an XML 230 * document, tries to create a Drawable from that tag. Returns {@code null} 231 * if the tag is not a valid drawable. 232 */ 233 public static AnimatedVectorDrawableCompat createFromXmlInner(Context context, Resources r, 234 XmlPullParser parser, AttributeSet attrs, Theme theme) 235 throws XmlPullParserException, IOException { 236 final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context); 237 drawable.inflate(r, parser, attrs, theme); 238 return drawable; 239 } 240 241 /** 242 * {@inheritDoc} 243 * <strong>Note</strong> that we don't support constant state when SDK < 24. 244 * Make sure you check the return value before using it. 245 */ 246 @Override 247 public ConstantState getConstantState() { 248 if (mDelegateDrawable != null) { 249 return new AnimatedVectorDrawableDelegateState(mDelegateDrawable.getConstantState()); 250 } 251 // We can't support constant state in older platform. 252 // We need Context to create the animator, and we can't save the context in the constant 253 // state. 254 return null; 255 } 256 257 @Override 258 public int getChangingConfigurations() { 259 if (mDelegateDrawable != null) { 260 return mDelegateDrawable.getChangingConfigurations(); 261 } 262 return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations; 263 } 264 265 @Override 266 public void draw(Canvas canvas) { 267 if (mDelegateDrawable != null) { 268 mDelegateDrawable.draw(canvas); 269 return; 270 } 271 mAnimatedVectorState.mVectorDrawable.draw(canvas); 272 if (isStarted()) { 273 invalidateSelf(); 274 } 275 } 276 277 @Override 278 protected void onBoundsChange(Rect bounds) { 279 if (mDelegateDrawable != null) { 280 mDelegateDrawable.setBounds(bounds); 281 return; 282 } 283 mAnimatedVectorState.mVectorDrawable.setBounds(bounds); 284 } 285 286 @Override 287 protected boolean onStateChange(int[] state) { 288 if (mDelegateDrawable != null) { 289 return mDelegateDrawable.setState(state); 290 } 291 return mAnimatedVectorState.mVectorDrawable.setState(state); 292 } 293 294 @Override 295 protected boolean onLevelChange(int level) { 296 if (mDelegateDrawable != null) { 297 return mDelegateDrawable.setLevel(level); 298 } 299 return mAnimatedVectorState.mVectorDrawable.setLevel(level); 300 } 301 302 @Override 303 public int getAlpha() { 304 if (mDelegateDrawable != null) { 305 return DrawableCompat.getAlpha(mDelegateDrawable); 306 } 307 return mAnimatedVectorState.mVectorDrawable.getAlpha(); 308 } 309 310 @Override 311 public void setAlpha(int alpha) { 312 if (mDelegateDrawable != null) { 313 mDelegateDrawable.setAlpha(alpha); 314 return; 315 } 316 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); 317 } 318 319 @Override 320 public void setColorFilter(ColorFilter colorFilter) { 321 if (mDelegateDrawable != null) { 322 mDelegateDrawable.setColorFilter(colorFilter); 323 return; 324 } 325 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); 326 } 327 328 @Override 329 public void setTint(int tint) { 330 if (mDelegateDrawable != null) { 331 DrawableCompat.setTint(mDelegateDrawable, tint); 332 return; 333 } 334 335 mAnimatedVectorState.mVectorDrawable.setTint(tint); 336 } 337 338 @Override 339 public void setTintList(ColorStateList tint) { 340 if (mDelegateDrawable != null) { 341 DrawableCompat.setTintList(mDelegateDrawable, tint); 342 return; 343 } 344 345 mAnimatedVectorState.mVectorDrawable.setTintList(tint); 346 } 347 348 @Override 349 public void setTintMode(PorterDuff.Mode tintMode) { 350 if (mDelegateDrawable != null) { 351 DrawableCompat.setTintMode(mDelegateDrawable, tintMode); 352 return; 353 } 354 355 mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode); 356 } 357 358 @Override 359 public boolean setVisible(boolean visible, boolean restart) { 360 if (mDelegateDrawable != null) { 361 return mDelegateDrawable.setVisible(visible, restart); 362 } 363 mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart); 364 return super.setVisible(visible, restart); 365 } 366 367 @Override 368 public boolean isStateful() { 369 if (mDelegateDrawable != null) { 370 return mDelegateDrawable.isStateful(); 371 } 372 return mAnimatedVectorState.mVectorDrawable.isStateful(); 373 } 374 375 @Override 376 public int getOpacity() { 377 if (mDelegateDrawable != null) { 378 return mDelegateDrawable.getOpacity(); 379 } 380 return mAnimatedVectorState.mVectorDrawable.getOpacity(); 381 } 382 383 @Override 384 public int getIntrinsicWidth() { 385 if (mDelegateDrawable != null) { 386 return mDelegateDrawable.getIntrinsicWidth(); 387 } 388 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); 389 } 390 391 @Override 392 public int getIntrinsicHeight() { 393 if (mDelegateDrawable != null) { 394 return mDelegateDrawable.getIntrinsicHeight(); 395 } 396 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); 397 } 398 399 @Override 400 public boolean isAutoMirrored() { 401 if (mDelegateDrawable != null) { 402 return DrawableCompat.isAutoMirrored(mDelegateDrawable); 403 } 404 return mAnimatedVectorState.mVectorDrawable.isAutoMirrored(); 405 } 406 407 @Override 408 public void setAutoMirrored(boolean mirrored) { 409 if (mDelegateDrawable != null) { 410 mDelegateDrawable.setAutoMirrored(mirrored); 411 return; 412 } 413 mAnimatedVectorState.mVectorDrawable.setAutoMirrored(mirrored); 414 } 415 416 /** 417 * Obtains styled attributes from the theme, if available, or unstyled 418 * resources if the theme is null. 419 */ 420 static TypedArray obtainAttributes( 421 Resources res, Theme theme, AttributeSet set, int[] attrs) { 422 if (theme == null) { 423 return res.obtainAttributes(set, attrs); 424 } 425 return theme.obtainStyledAttributes(set, attrs, 0, 0); 426 } 427 428 @Override 429 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 430 throws XmlPullParserException, IOException { 431 if (mDelegateDrawable != null) { 432 DrawableCompat.inflate(mDelegateDrawable, res, parser, attrs, theme); 433 return; 434 } 435 int eventType = parser.getEventType(); 436 final int innerDepth = parser.getDepth() + 1; 437 438 // Parse everything until the end of the animated-vector element. 439 while (eventType != XmlPullParser.END_DOCUMENT 440 && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) { 441 if (eventType == XmlPullParser.START_TAG) { 442 final String tagName = parser.getName(); 443 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 444 Log.v(LOGTAG, "tagName is " + tagName); 445 } 446 if (ANIMATED_VECTOR.equals(tagName)) { 447 final TypedArray a = 448 obtainAttributes(res, theme, attrs, 449 AndroidResources.styleable_AnimatedVectorDrawable); 450 451 int drawableRes = a.getResourceId( 452 AndroidResources.styleable_AnimatedVectorDrawable_drawable, 0); 453 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 454 Log.v(LOGTAG, "drawableRes is " + drawableRes); 455 } 456 if (drawableRes != 0) { 457 VectorDrawableCompat vectorDrawable = VectorDrawableCompat.create(res, 458 drawableRes, theme); 459 vectorDrawable.setAllowCaching(false); 460 vectorDrawable.setCallback(mCallback); 461 if (mAnimatedVectorState.mVectorDrawable != null) { 462 mAnimatedVectorState.mVectorDrawable.setCallback(null); 463 } 464 mAnimatedVectorState.mVectorDrawable = vectorDrawable; 465 } 466 a.recycle(); 467 } else if (TARGET.equals(tagName)) { 468 final TypedArray a = 469 res.obtainAttributes(attrs, 470 AndroidResources.styleable_AnimatedVectorDrawableTarget); 471 final String target = a.getString( 472 AndroidResources.styleable_AnimatedVectorDrawableTarget_name); 473 474 int id = a.getResourceId( 475 AndroidResources.styleable_AnimatedVectorDrawableTarget_animation, 0); 476 if (id != 0) { 477 if (mContext != null) { 478 Animator objectAnimator = AnimatorInflater.loadAnimator(mContext, id); 479 setupAnimatorsForTarget(target, objectAnimator); 480 } else { 481 throw new IllegalStateException("Context can't be null when inflating" + 482 " animators"); 483 } 484 } 485 a.recycle(); 486 } 487 } 488 489 eventType = parser.next(); 490 } 491 } 492 493 @Override 494 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs) 495 throws XmlPullParserException, IOException { 496 inflate(res, parser, attrs, null); 497 } 498 499 @Override 500 public void applyTheme(Theme t) { 501 if (mDelegateDrawable != null) { 502 DrawableCompat.applyTheme(mDelegateDrawable, t); 503 return; 504 } 505 // TODO: support theming in older platform. 506 return; 507 } 508 509 @Override 510 public boolean canApplyTheme() { 511 if (mDelegateDrawable != null) { 512 return DrawableCompat.canApplyTheme(mDelegateDrawable); 513 } 514 // TODO: support theming in older platform. 515 return false; 516 } 517 518 /** 519 * Constant state for delegating the creating drawable job. 520 * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains 521 * a delegated VectorDrawable instance. 522 */ 523 private static class AnimatedVectorDrawableDelegateState extends ConstantState { 524 private final ConstantState mDelegateState; 525 526 public AnimatedVectorDrawableDelegateState(ConstantState state) { 527 mDelegateState = state; 528 } 529 530 @Override 531 public Drawable newDrawable() { 532 AnimatedVectorDrawableCompat drawableCompat = 533 new AnimatedVectorDrawableCompat(); 534 drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(); 535 drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback); 536 return drawableCompat; 537 } 538 539 @Override 540 public Drawable newDrawable(Resources res) { 541 AnimatedVectorDrawableCompat drawableCompat = 542 new AnimatedVectorDrawableCompat(); 543 drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res); 544 drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback); 545 return drawableCompat; 546 } 547 548 @Override 549 public Drawable newDrawable(Resources res, Theme theme) { 550 AnimatedVectorDrawableCompat drawableCompat = 551 new AnimatedVectorDrawableCompat(); 552 drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res, theme); 553 drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback); 554 return drawableCompat; 555 } 556 557 @Override 558 public boolean canApplyTheme() { 559 return mDelegateState.canApplyTheme(); 560 } 561 562 @Override 563 public int getChangingConfigurations() { 564 return mDelegateState.getChangingConfigurations(); 565 } 566 } 567 568 private static class AnimatedVectorDrawableCompatState extends ConstantState { 569 int mChangingConfigurations; 570 VectorDrawableCompat mVectorDrawable; 571 ArrayList<Animator> mAnimators; 572 ArrayMap<Animator, String> mTargetNameMap; 573 574 public AnimatedVectorDrawableCompatState(Context context, 575 AnimatedVectorDrawableCompatState copy, Callback owner, Resources res) { 576 if (copy != null) { 577 mChangingConfigurations = copy.mChangingConfigurations; 578 if (copy.mVectorDrawable != null) { 579 final ConstantState cs = copy.mVectorDrawable.getConstantState(); 580 if (res != null) { 581 mVectorDrawable = (VectorDrawableCompat) cs.newDrawable(res); 582 } else { 583 mVectorDrawable = (VectorDrawableCompat) cs.newDrawable(); 584 } 585 mVectorDrawable = (VectorDrawableCompat) mVectorDrawable.mutate(); 586 mVectorDrawable.setCallback(owner); 587 mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds()); 588 mVectorDrawable.setAllowCaching(false); 589 } 590 if (copy.mAnimators != null) { 591 final int numAnimators = copy.mAnimators.size(); 592 mAnimators = new ArrayList<Animator>(numAnimators); 593 mTargetNameMap = new ArrayMap<Animator, String>(numAnimators); 594 for (int i = 0; i < numAnimators; ++i) { 595 Animator anim = copy.mAnimators.get(i); 596 Animator animClone = anim.clone(); 597 String targetName = copy.mTargetNameMap.get(anim); 598 Object targetObject = mVectorDrawable.getTargetByName(targetName); 599 animClone.setTarget(targetObject); 600 mAnimators.add(animClone); 601 mTargetNameMap.put(animClone, targetName); 602 } 603 } 604 } 605 } 606 607 @Override 608 public Drawable newDrawable() { 609 throw new IllegalStateException("No constant state support for SDK < 24."); 610 } 611 612 @Override 613 public Drawable newDrawable(Resources res) { 614 throw new IllegalStateException("No constant state support for SDK < 24."); 615 } 616 617 @Override 618 public int getChangingConfigurations() { 619 return mChangingConfigurations; 620 } 621 } 622 623 /** 624 * Utility function to fix color interpolation prior to Lollipop. Without this fix, colors 625 * are evaluated as raw integers instead of as colors, which leads to artifacts during 626 * fillColor animations. 627 */ 628 private void setupColorAnimator(Animator animator) { 629 if (animator instanceof AnimatorSet) { 630 List<Animator> childAnimators = ((AnimatorSet) animator).getChildAnimations(); 631 if (childAnimators != null) { 632 for (int i = 0; i < childAnimators.size(); ++i) { 633 setupColorAnimator(childAnimators.get(i)); 634 } 635 } 636 } 637 if (animator instanceof ObjectAnimator) { 638 ObjectAnimator objectAnim = (ObjectAnimator) animator; 639 final String propertyName = objectAnim.getPropertyName(); 640 if ("fillColor".equals(propertyName) || "strokeColor".equals(propertyName)) { 641 if (mArgbEvaluator == null) { 642 mArgbEvaluator = new ArgbEvaluator(); 643 } 644 objectAnim.setEvaluator(mArgbEvaluator); 645 } 646 } 647 } 648 649 private void setupAnimatorsForTarget(String name, Animator animator) { 650 Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name); 651 animator.setTarget(target); 652 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 653 setupColorAnimator(animator); 654 } 655 if (mAnimatedVectorState.mAnimators == null) { 656 mAnimatedVectorState.mAnimators = new ArrayList<Animator>(); 657 mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>(); 658 } 659 mAnimatedVectorState.mAnimators.add(animator); 660 mAnimatedVectorState.mTargetNameMap.put(animator, name); 661 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 662 Log.v(LOGTAG, "add animator for target " + name + " " + animator); 663 } 664 } 665 666 @Override 667 public boolean isRunning() { 668 if (mDelegateDrawable != null) { 669 return ((AnimatedVectorDrawable) mDelegateDrawable).isRunning(); 670 } 671 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 672 final int size = animators.size(); 673 for (int i = 0; i < size; i++) { 674 final Animator animator = animators.get(i); 675 if (animator.isRunning()) { 676 return true; 677 } 678 } 679 return false; 680 } 681 682 private boolean isStarted() { 683 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 684 if (animators == null) { 685 return false; 686 } 687 final int size = animators.size(); 688 for (int i = 0; i < size; i++) { 689 final Animator animator = animators.get(i); 690 if (animator.isRunning()) { 691 return true; 692 } 693 } 694 return false; 695 } 696 697 @Override 698 public void start() { 699 if (mDelegateDrawable != null) { 700 ((AnimatedVectorDrawable) mDelegateDrawable).start(); 701 return; 702 } 703 // If any one of the animator has not ended, do nothing. 704 if (isStarted()) { 705 return; 706 } 707 // Otherwise, kick off every animator. 708 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 709 final int size = animators.size(); 710 for (int i = 0; i < size; i++) { 711 final Animator animator = animators.get(i); 712 animator.start(); 713 } 714 invalidateSelf(); 715 } 716 717 @Override 718 public void stop() { 719 if (mDelegateDrawable != null) { 720 ((AnimatedVectorDrawable) mDelegateDrawable).stop(); 721 return; 722 } 723 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 724 final int size = animators.size(); 725 for (int i = 0; i < size; i++) { 726 final Animator animator = animators.get(i); 727 animator.end(); 728 } 729 } 730 731 final Callback mCallback = new Callback() { 732 @Override 733 public void invalidateDrawable(Drawable who) { 734 invalidateSelf(); 735 } 736 737 @Override 738 public void scheduleDrawable(Drawable who, Runnable what, long when) { 739 scheduleSelf(what, when); 740 } 741 742 @Override 743 public void unscheduleDrawable(Drawable who, Runnable what) { 744 unscheduleSelf(what); 745 } 746 }; 747} 748