AnimatedVectorDrawable.java revision 766431aa57c16ece8842287a92b2e7208e3b8ac3
1/* 2 * Copyright (C) 2014 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.graphics.drawable; 16 17import android.animation.Animator; 18import android.animation.AnimatorInflater; 19import android.animation.AnimatorListenerAdapter; 20import android.animation.AnimatorSet; 21import android.animation.Animator.AnimatorListener; 22import android.animation.PropertyValuesHolder; 23import android.animation.TimeInterpolator; 24import android.animation.ValueAnimator; 25import android.animation.ObjectAnimator; 26import android.annotation.NonNull; 27import android.annotation.Nullable; 28import android.content.res.ColorStateList; 29import android.content.res.Resources; 30import android.content.res.Resources.Theme; 31import android.content.res.TypedArray; 32import android.graphics.Canvas; 33import android.graphics.ColorFilter; 34import android.graphics.Insets; 35import android.graphics.Outline; 36import android.graphics.PorterDuff; 37import android.graphics.Rect; 38import android.util.ArrayMap; 39import android.util.AttributeSet; 40import android.util.Log; 41import android.util.LongArray; 42import android.util.PathParser; 43import android.util.TimeUtils; 44import android.view.Choreographer; 45import android.view.DisplayListCanvas; 46import android.view.RenderNode; 47import android.view.RenderNodeAnimatorSetHelper; 48import android.view.View; 49 50import com.android.internal.R; 51 52import com.android.internal.util.VirtualRefBasePtr; 53import org.xmlpull.v1.XmlPullParser; 54import org.xmlpull.v1.XmlPullParserException; 55 56import java.io.IOException; 57import java.lang.ref.WeakReference; 58import java.util.ArrayList; 59 60/** 61 * This class uses {@link android.animation.ObjectAnimator} and 62 * {@link android.animation.AnimatorSet} to animate the properties of a 63 * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable. 64 * <p> 65 * AnimatedVectorDrawable are normally defined as 3 separate XML files. 66 * </p> 67 * <p> 68 * First is the XML file for {@link android.graphics.drawable.VectorDrawable}. 69 * Note that we allow the animation to happen on the group's attributes and path's 70 * attributes, which requires they are uniquely named in this XML file. Groups 71 * and paths without animations do not need names. 72 * </p> 73 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file. 74 * <pre> 75 * <vector xmlns:android="http://schemas.android.com/apk/res/android" 76 * android:height="64dp" 77 * android:width="64dp" 78 * android:viewportHeight="600" 79 * android:viewportWidth="600" > 80 * <group 81 * android:name="rotationGroup" 82 * android:pivotX="300.0" 83 * android:pivotY="300.0" 84 * android:rotation="45.0" > 85 * <path 86 * android:name="v" 87 * android:fillColor="#000000" 88 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 89 * </group> 90 * </vector> 91 * </pre></li> 92 * <p> 93 * Second is the AnimatedVectorDrawable's XML file, which defines the target 94 * VectorDrawable, the target paths and groups to animate, the properties of the 95 * path and group to animate and the animations defined as the ObjectAnimators 96 * or AnimatorSets. 97 * </p> 98 * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file. 99 * Note how we use the names to refer to the groups and paths in the vectordrawable.xml. 100 * <pre> 101 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" 102 * android:drawable="@drawable/vectordrawable" > 103 * <target 104 * android:name="rotationGroup" 105 * android:animation="@anim/rotation" /> 106 * <target 107 * android:name="v" 108 * android:animation="@anim/path_morph" /> 109 * </animated-vector> 110 * </pre></li> 111 * <p> 112 * Last is the Animator XML file, which is the same as a normal ObjectAnimator 113 * or AnimatorSet. 114 * To complete this example, here are the 2 animator files used in avd.xml: 115 * rotation.xml and path_morph.xml. 116 * </p> 117 * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees. 118 * <pre> 119 * <objectAnimator 120 * android:duration="6000" 121 * android:propertyName="rotation" 122 * android:valueFrom="0" 123 * android:valueTo="360" /> 124 * </pre></li> 125 * <li>Here is the path_morph.xml, which will morph the path from one shape to 126 * the other. Note that the paths must be compatible for morphing. 127 * In more details, the paths should have exact same length of commands , and 128 * exact same length of parameters for each commands. 129 * Note that the path strings are better stored in strings.xml for reusing. 130 * <pre> 131 * <set xmlns:android="http://schemas.android.com/apk/res/android"> 132 * <objectAnimator 133 * android:duration="3000" 134 * android:propertyName="pathData" 135 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z" 136 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" 137 * android:valueType="pathType"/> 138 * </set> 139 * </pre></li> 140 * 141 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable 142 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name 143 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation 144 */ 145public class AnimatedVectorDrawable extends Drawable implements Animatable2 { 146 private static final String LOGTAG = "AnimatedVectorDrawable"; 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 /** Local, mutable animator set. */ 154 private final VectorDrawableAnimator mAnimatorSet = new VectorDrawableAnimator(); 155 156 /** 157 * The resources against which this drawable was created. Used to attempt 158 * to inflate animators if applyTheme() doesn't get called. 159 */ 160 private Resources mRes; 161 162 private AnimatedVectorDrawableState mAnimatedVectorState; 163 164 /** Whether the animator set has been prepared. */ 165 private boolean mHasAnimatorSet; 166 167 private boolean mMutated; 168 169 /** Use a internal AnimatorListener to support callbacks during animation events. */ 170 private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null; 171 private AnimatorListener mAnimatorListener = null; 172 173 public AnimatedVectorDrawable() { 174 this(null, null); 175 } 176 177 private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) { 178 mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res); 179 mRes = res; 180 } 181 182 @Override 183 public Drawable mutate() { 184 if (!mMutated && super.mutate() == this) { 185 mAnimatedVectorState = new AnimatedVectorDrawableState( 186 mAnimatedVectorState, mCallback, mRes); 187 mMutated = true; 188 } 189 return this; 190 } 191 192 /** 193 * @hide 194 */ 195 public void clearMutated() { 196 super.clearMutated(); 197 if (mAnimatedVectorState.mVectorDrawable != null) { 198 mAnimatedVectorState.mVectorDrawable.clearMutated(); 199 } 200 mMutated = false; 201 } 202 203 @Override 204 public ConstantState getConstantState() { 205 mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations(); 206 return mAnimatedVectorState; 207 } 208 209 @Override 210 public int getChangingConfigurations() { 211 return super.getChangingConfigurations() | mAnimatedVectorState.getChangingConfigurations(); 212 } 213 214 @Override 215 public void draw(Canvas canvas) { 216 if (canvas.isHardwareAccelerated()) { 217 mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas); 218 } 219 mAnimatedVectorState.mVectorDrawable.draw(canvas); 220 if (isStarted()) { 221 invalidateSelf(); 222 } 223 } 224 225 @Override 226 protected void onBoundsChange(Rect bounds) { 227 mAnimatedVectorState.mVectorDrawable.setBounds(bounds); 228 } 229 230 @Override 231 protected boolean onStateChange(int[] state) { 232 return mAnimatedVectorState.mVectorDrawable.setState(state); 233 } 234 235 @Override 236 protected boolean onLevelChange(int level) { 237 return mAnimatedVectorState.mVectorDrawable.setLevel(level); 238 } 239 240 @Override 241 public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) { 242 return mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection); 243 } 244 245 @Override 246 public int getAlpha() { 247 return mAnimatedVectorState.mVectorDrawable.getAlpha(); 248 } 249 250 @Override 251 public void setAlpha(int alpha) { 252 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); 253 } 254 255 @Override 256 public void setColorFilter(ColorFilter colorFilter) { 257 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); 258 } 259 260 @Override 261 public void setTintList(ColorStateList tint) { 262 mAnimatedVectorState.mVectorDrawable.setTintList(tint); 263 } 264 265 @Override 266 public void setHotspot(float x, float y) { 267 mAnimatedVectorState.mVectorDrawable.setHotspot(x, y); 268 } 269 270 @Override 271 public void setHotspotBounds(int left, int top, int right, int bottom) { 272 mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom); 273 } 274 275 @Override 276 public void setTintMode(PorterDuff.Mode tintMode) { 277 mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode); 278 } 279 280 @Override 281 public boolean setVisible(boolean visible, boolean restart) { 282 mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart); 283 return super.setVisible(visible, restart); 284 } 285 286 @Override 287 public boolean isStateful() { 288 return mAnimatedVectorState.mVectorDrawable.isStateful(); 289 } 290 291 @Override 292 public int getOpacity() { 293 return mAnimatedVectorState.mVectorDrawable.getOpacity(); 294 } 295 296 @Override 297 public int getIntrinsicWidth() { 298 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); 299 } 300 301 @Override 302 public int getIntrinsicHeight() { 303 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); 304 } 305 306 @Override 307 public void getOutline(@NonNull Outline outline) { 308 mAnimatedVectorState.mVectorDrawable.getOutline(outline); 309 } 310 311 /** @hide */ 312 @Override 313 public Insets getOpticalInsets() { 314 return mAnimatedVectorState.mVectorDrawable.getOpticalInsets(); 315 } 316 317 @Override 318 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 319 throws XmlPullParserException, IOException { 320 final AnimatedVectorDrawableState state = mAnimatedVectorState; 321 322 int eventType = parser.getEventType(); 323 float pathErrorScale = 1; 324 while (eventType != XmlPullParser.END_DOCUMENT) { 325 if (eventType == XmlPullParser.START_TAG) { 326 final String tagName = parser.getName(); 327 if (ANIMATED_VECTOR.equals(tagName)) { 328 final TypedArray a = obtainAttributes(res, theme, attrs, 329 R.styleable.AnimatedVectorDrawable); 330 int drawableRes = a.getResourceId( 331 R.styleable.AnimatedVectorDrawable_drawable, 0); 332 if (drawableRes != 0) { 333 VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable( 334 drawableRes, theme).mutate(); 335 vectorDrawable.setAllowCaching(false); 336 vectorDrawable.setCallback(mCallback); 337 pathErrorScale = vectorDrawable.getPixelSize(); 338 if (state.mVectorDrawable != null) { 339 state.mVectorDrawable.setCallback(null); 340 } 341 state.mVectorDrawable = vectorDrawable; 342 } 343 a.recycle(); 344 } else if (TARGET.equals(tagName)) { 345 final TypedArray a = obtainAttributes(res, theme, attrs, 346 R.styleable.AnimatedVectorDrawableTarget); 347 final String target = a.getString( 348 R.styleable.AnimatedVectorDrawableTarget_name); 349 final int animResId = a.getResourceId( 350 R.styleable.AnimatedVectorDrawableTarget_animation, 0); 351 if (animResId != 0) { 352 if (theme != null) { 353 final Animator objectAnimator = AnimatorInflater.loadAnimator( 354 res, theme, animResId, pathErrorScale); 355 state.addTargetAnimator(target, objectAnimator); 356 } else { 357 // The animation may be theme-dependent. As a 358 // workaround until Animator has full support for 359 // applyTheme(), postpone loading the animator 360 // until we have a theme in applyTheme(). 361 state.addPendingAnimator(animResId, pathErrorScale, target); 362 363 } 364 } 365 a.recycle(); 366 } 367 } 368 369 eventType = parser.next(); 370 } 371 372 // If we don't have any pending animations, we don't need to hold a 373 // reference to the resources. 374 mRes = state.mPendingAnims == null ? null : res; 375 } 376 377 @Override 378 public boolean canApplyTheme() { 379 return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme()) 380 || super.canApplyTheme(); 381 } 382 383 @Override 384 public void applyTheme(Theme t) { 385 super.applyTheme(t); 386 387 final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable; 388 if (vectorDrawable != null && vectorDrawable.canApplyTheme()) { 389 vectorDrawable.applyTheme(t); 390 } 391 392 if (t != null) { 393 mAnimatedVectorState.inflatePendingAnimators(t.getResources(), t); 394 } 395 396 // If we don't have any pending animations, we don't need to hold a 397 // reference to the resources. 398 if (mAnimatedVectorState.mPendingAnims == null) { 399 mRes = null; 400 } 401 } 402 403 private static class AnimatedVectorDrawableState extends ConstantState { 404 int mChangingConfigurations; 405 VectorDrawable mVectorDrawable; 406 407 /** Animators that require a theme before inflation. */ 408 ArrayList<PendingAnimator> mPendingAnims; 409 410 /** Fully inflated animators awaiting cloning into an AnimatorSet. */ 411 ArrayList<Animator> mAnimators; 412 413 /** Map of animators to their target object names */ 414 ArrayMap<Animator, String> mTargetNameMap; 415 416 public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy, 417 Callback owner, Resources res) { 418 if (copy != null) { 419 mChangingConfigurations = copy.mChangingConfigurations; 420 421 if (copy.mVectorDrawable != null) { 422 final ConstantState cs = copy.mVectorDrawable.getConstantState(); 423 if (res != null) { 424 mVectorDrawable = (VectorDrawable) cs.newDrawable(res); 425 } else { 426 mVectorDrawable = (VectorDrawable) cs.newDrawable(); 427 } 428 mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate(); 429 mVectorDrawable.setCallback(owner); 430 mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection()); 431 mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds()); 432 mVectorDrawable.setAllowCaching(false); 433 } 434 435 if (copy.mAnimators != null) { 436 mAnimators = new ArrayList<>(copy.mAnimators); 437 } 438 439 if (copy.mTargetNameMap != null) { 440 mTargetNameMap = new ArrayMap<>(copy.mTargetNameMap); 441 } 442 443 if (copy.mPendingAnims != null) { 444 mPendingAnims = new ArrayList<>(copy.mPendingAnims); 445 } 446 } else { 447 mVectorDrawable = new VectorDrawable(); 448 } 449 } 450 451 @Override 452 public boolean canApplyTheme() { 453 return (mVectorDrawable != null && mVectorDrawable.canApplyTheme()) 454 || mPendingAnims != null || super.canApplyTheme(); 455 } 456 457 @Override 458 public Drawable newDrawable() { 459 return new AnimatedVectorDrawable(this, null); 460 } 461 462 @Override 463 public Drawable newDrawable(Resources res) { 464 return new AnimatedVectorDrawable(this, res); 465 } 466 467 @Override 468 public int getChangingConfigurations() { 469 return mChangingConfigurations; 470 } 471 472 public void addPendingAnimator(int resId, float pathErrorScale, String target) { 473 if (mPendingAnims == null) { 474 mPendingAnims = new ArrayList<>(1); 475 } 476 mPendingAnims.add(new PendingAnimator(resId, pathErrorScale, target)); 477 } 478 479 public void addTargetAnimator(String targetName, Animator animator) { 480 if (mAnimators == null) { 481 mAnimators = new ArrayList<>(1); 482 mTargetNameMap = new ArrayMap<>(1); 483 } 484 mAnimators.add(animator); 485 mTargetNameMap.put(animator, targetName); 486 487 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 488 Log.v(LOGTAG, "add animator for target " + targetName + " " + animator); 489 } 490 } 491 492 /** 493 * Prepares a local set of mutable animators based on the constant 494 * state. 495 * <p> 496 * If there are any pending uninflated animators, attempts to inflate 497 * them immediately against the provided resources object. 498 * 499 * @param animatorSet the animator set to which the animators should 500 * be added 501 * @param res the resources against which to inflate any pending 502 * animators, or {@code null} if not available 503 */ 504 public void prepareLocalAnimators(@NonNull AnimatorSet animatorSet, 505 @Nullable Resources res) { 506 // Check for uninflated animators. We can remove this after we add 507 // support for Animator.applyTheme(). See comments in inflate(). 508 if (mPendingAnims != null) { 509 // Attempt to load animators without applying a theme. 510 if (res != null) { 511 inflatePendingAnimators(res, null); 512 } else { 513 Log.e(LOGTAG, "Failed to load animators. Either the AnimatedVectorDrawable" 514 + " must be created using a Resources object or applyTheme() must be" 515 + " called with a non-null Theme object."); 516 } 517 518 mPendingAnims = null; 519 } 520 521 // Perform a deep copy of the constant state's animators. 522 final int count = mAnimators == null ? 0 : mAnimators.size(); 523 if (count > 0) { 524 final Animator firstAnim = prepareLocalAnimator(0); 525 final AnimatorSet.Builder builder = animatorSet.play(firstAnim); 526 for (int i = 1; i < count; ++i) { 527 final Animator nextAnim = prepareLocalAnimator(i); 528 builder.with(nextAnim); 529 } 530 } 531 } 532 533 /** 534 * Prepares a local animator for the given index within the constant 535 * state's list of animators. 536 * 537 * @param index the index of the animator within the constant state 538 */ 539 private Animator prepareLocalAnimator(int index) { 540 final Animator animator = mAnimators.get(index); 541 final Animator localAnimator = animator.clone(); 542 final String targetName = mTargetNameMap.get(animator); 543 final Object target = mVectorDrawable.getTargetByName(targetName); 544 localAnimator.setTarget(target); 545 return localAnimator; 546 } 547 548 /** 549 * Inflates pending animators, if any, against a theme. Clears the list of 550 * pending animators. 551 * 552 * @param t the theme against which to inflate the animators 553 */ 554 public void inflatePendingAnimators(@NonNull Resources res, @Nullable Theme t) { 555 final ArrayList<PendingAnimator> pendingAnims = mPendingAnims; 556 if (pendingAnims != null) { 557 mPendingAnims = null; 558 559 for (int i = 0, count = pendingAnims.size(); i < count; i++) { 560 final PendingAnimator pendingAnimator = pendingAnims.get(i); 561 final Animator objectAnimator = pendingAnimator.newInstance(res, t); 562 addTargetAnimator(pendingAnimator.target, objectAnimator); 563 } 564 } 565 } 566 567 /** 568 * Basically a constant state for Animators until we actually implement 569 * constant states for Animators. 570 */ 571 private static class PendingAnimator { 572 public final int animResId; 573 public final float pathErrorScale; 574 public final String target; 575 576 public PendingAnimator(int animResId, float pathErrorScale, String target) { 577 this.animResId = animResId; 578 this.pathErrorScale = pathErrorScale; 579 this.target = target; 580 } 581 582 public Animator newInstance(Resources res, Theme theme) { 583 return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale); 584 } 585 } 586 } 587 588 @Override 589 public boolean isRunning() { 590 return mAnimatorSet.isRunning(); 591 } 592 593 private boolean isStarted() { 594 return mAnimatorSet.isStarted(); 595 } 596 597 /** 598 * Resets the AnimatedVectorDrawable to the start state as specified in the animators. 599 */ 600 public void reset() { 601 mAnimatorSet.reset(); 602 invalidateSelf(); 603 } 604 605 @Override 606 public void start() { 607 ensureAnimatorSet(); 608 609 // If any one of the animator has not ended, do nothing. 610 if (isStarted()) { 611 return; 612 } 613 614 mAnimatorSet.start(); 615 invalidateSelf(); 616 } 617 618 @NonNull 619 private void ensureAnimatorSet() { 620 if (!mHasAnimatorSet) { 621 // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly 622 // with a list of LocalAnimators. 623 AnimatorSet set = new AnimatorSet(); 624 mAnimatedVectorState.prepareLocalAnimators(set, mRes); 625 mHasAnimatorSet = true; 626 mAnimatorSet.initWithAnimatorSet(set); 627 mRes = null; 628 } 629 } 630 631 @Override 632 public void stop() { 633 mAnimatorSet.end(); 634 } 635 636 /** 637 * Reverses ongoing animations or starts pending animations in reverse. 638 * <p> 639 * NOTE: Only works if all animations support reverse. Otherwise, this will 640 * do nothing. 641 * @hide 642 */ 643 public void reverse() { 644 ensureAnimatorSet(); 645 646 // Only reverse when all the animators can be reversed. 647 if (!canReverse()) { 648 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); 649 return; 650 } 651 652 mAnimatorSet.reverse(); 653 invalidateSelf(); 654 } 655 656 /** 657 * @hide 658 */ 659 public boolean canReverse() { 660 return mAnimatorSet.canReverse(); 661 } 662 663 private final Callback mCallback = new Callback() { 664 @Override 665 public void invalidateDrawable(Drawable who) { 666 invalidateSelf(); 667 } 668 669 @Override 670 public void scheduleDrawable(Drawable who, Runnable what, long when) { 671 scheduleSelf(what, when); 672 } 673 674 @Override 675 public void unscheduleDrawable(Drawable who, Runnable what) { 676 unscheduleSelf(what); 677 } 678 }; 679 680 @Override 681 public void registerAnimationCallback(@NonNull AnimationCallback callback) { 682 if (callback == null) { 683 return; 684 } 685 686 // Add listener accordingly. 687 if (mAnimationCallbacks == null) { 688 mAnimationCallbacks = new ArrayList<>(); 689 } 690 691 mAnimationCallbacks.add(callback); 692 693 if (mAnimatorListener == null) { 694 // Create a animator listener and trigger the callback events when listener is 695 // triggered. 696 mAnimatorListener = new AnimatorListenerAdapter() { 697 @Override 698 public void onAnimationStart(Animator animation) { 699 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks); 700 int size = tmpCallbacks.size(); 701 for (int i = 0; i < size; i ++) { 702 tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawable.this); 703 } 704 } 705 706 @Override 707 public void onAnimationEnd(Animator animation) { 708 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks); 709 int size = tmpCallbacks.size(); 710 for (int i = 0; i < size; i ++) { 711 tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawable.this); 712 } 713 } 714 }; 715 } 716 mAnimatorSet.setListener(mAnimatorListener); 717 } 718 719 // A helper function to clean up the animator listener in the mAnimatorSet. 720 private void removeAnimatorSetListener() { 721 if (mAnimatorListener != null) { 722 mAnimatorSet.removeListener(); 723 mAnimatorListener = null; 724 } 725 } 726 727 @Override 728 public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) { 729 if (mAnimationCallbacks == null || callback == null) { 730 // Nothing to be removed. 731 return false; 732 } 733 boolean removed = mAnimationCallbacks.remove(callback); 734 735 // When the last call back unregistered, remove the listener accordingly. 736 if (mAnimationCallbacks.size() == 0) { 737 removeAnimatorSetListener(); 738 } 739 return removed; 740 } 741 742 @Override 743 public void clearAnimationCallbacks() { 744 removeAnimatorSetListener(); 745 if (mAnimationCallbacks == null) { 746 return; 747 } 748 749 mAnimationCallbacks.clear(); 750 } 751 752 /** 753 * @hide 754 */ 755 public static class VectorDrawableAnimator { 756 private AnimatorListener mListener = null; 757 private final LongArray mStartDelays = new LongArray(); 758 private PropertyValuesHolder.PropertyValues mTmpValues = 759 new PropertyValuesHolder.PropertyValues(); 760 private long mSetPtr = 0; 761 private boolean mContainsSequentialAnimators = false; 762 private boolean mStarted = false; 763 private boolean mInitialized = false; 764 private boolean mAnimationPending = false; 765 private boolean mIsReversible = false; 766 // TODO: Consider using NativeAllocationRegistery to track native allocation 767 private final VirtualRefBasePtr mSetRefBasePtr; 768 private WeakReference<RenderNode> mTarget = null; 769 private WeakReference<RenderNode> mLastSeenTarget = null; 770 771 772 VectorDrawableAnimator() { 773 mSetPtr = nCreateAnimatorSet(); 774 // Increment ref count on native AnimatorSet, so it doesn't get released before Java 775 // side is done using it. 776 mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr); 777 } 778 779 private void initWithAnimatorSet(AnimatorSet set) { 780 if (mInitialized) { 781 // Already initialized 782 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " + 783 "re-initialized"); 784 } 785 parseAnimatorSet(set, 0); 786 mInitialized = true; 787 788 // Check reversible. 789 if (mContainsSequentialAnimators) { 790 mIsReversible = false; 791 } else { 792 // Check if there's any start delay set on child 793 for (int i = 0; i < mStartDelays.size(); i++) { 794 if (mStartDelays.get(i) > 0) { 795 mIsReversible = false; 796 return; 797 } 798 } 799 } 800 mIsReversible = true; 801 } 802 803 private void parseAnimatorSet(AnimatorSet set, long startTime) { 804 ArrayList<Animator> animators = set.getChildAnimations(); 805 806 boolean playTogether = set.shouldPlayTogether(); 807 // Convert AnimatorSet to VectorDrawableAnimator 808 for (int i = 0; i < animators.size(); i++) { 809 Animator animator = animators.get(i); 810 // Here we only support ObjectAnimator 811 if (animator instanceof AnimatorSet) { 812 parseAnimatorSet((AnimatorSet) animator, startTime); 813 } else if (animator instanceof ObjectAnimator) { 814 createRTAnimator((ObjectAnimator) animator, startTime); 815 } // ignore ValueAnimators and others because they don't directly modify VD 816 // therefore will be useless to AVD. 817 818 if (!playTogether) { 819 // Assume not play together means play sequentially 820 startTime += animator.getTotalDuration(); 821 mContainsSequentialAnimators = true; 822 } 823 } 824 } 825 826 // TODO: This method reads animation data from already parsed Animators. We need to move 827 // this step further up the chain in the parser to avoid the detour. 828 private void createRTAnimator(ObjectAnimator animator, long startTime) { 829 PropertyValuesHolder[] values = animator.getValues(); 830 Object target = animator.getTarget(); 831 if (target instanceof VectorDrawable.VGroup) { 832 createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target, 833 startTime); 834 } else if (target instanceof VectorDrawable.VPath) { 835 for (int i = 0; i < values.length; i++) { 836 values[i].getPropertyValues(mTmpValues); 837 if (mTmpValues.endValue instanceof PathParser.PathData && 838 mTmpValues.propertyName.equals("pathData")) { 839 createRTAnimatorForPath(animator, (VectorDrawable.VPath) target, 840 startTime); 841 } else if (target instanceof VectorDrawable.VFullPath) { 842 createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target, 843 startTime); 844 } else { 845 throw new IllegalArgumentException("ClipPath only supports PathData " + 846 "property"); 847 } 848 849 } 850 } else if (target instanceof VectorDrawable.VectorDrawableState) { 851 createRTAnimatorForRootGroup(values, animator, 852 (VectorDrawable.VectorDrawableState) target, startTime); 853 } else { 854 // Should never get here 855 throw new UnsupportedOperationException("Target should be either VGroup, VPath, " + 856 "or ConstantState, " + target.getClass() + " is not supported"); 857 } 858 } 859 860 private void createRTAnimatorForGroup(PropertyValuesHolder[] values, 861 ObjectAnimator animator, VectorDrawable.VGroup target, 862 long startTime) { 863 864 long nativePtr = target.getNativePtr(); 865 int propertyId; 866 for (int i = 0; i < values.length; i++) { 867 // TODO: We need to support the rare case in AVD where no start value is provided 868 values[i].getPropertyValues(mTmpValues); 869 propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName); 870 if (mTmpValues.type != Float.class && mTmpValues.type != float.class) { 871 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 872 Log.e(LOGTAG, "Unsupported type: " + 873 mTmpValues.type + ". Only float value is supported for Groups."); 874 } 875 continue; 876 } 877 if (propertyId < 0) { 878 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 879 Log.e(LOGTAG, "Unsupported property: " + 880 mTmpValues.propertyName + " for Vector Drawable Group"); 881 } 882 continue; 883 } 884 long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId, 885 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 886 if (mTmpValues.dataSource != null) { 887 float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator 888 .getDuration()); 889 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 890 } 891 createNativeChildAnimator(propertyPtr, startTime, animator); 892 } 893 } 894 private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target, 895 long startTime) { 896 897 long nativePtr = target.getNativePtr(); 898 long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue) 899 .getNativePtr(); 900 long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue) 901 .getNativePtr(); 902 long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr, 903 endPathDataPtr); 904 createNativeChildAnimator(propertyPtr, startTime, animator); 905 } 906 907 private void createRTAnimatorForFullPath(ObjectAnimator animator, 908 VectorDrawable.VFullPath target, long startTime) { 909 910 int propertyId = target.getPropertyIndex(mTmpValues.propertyName); 911 long propertyPtr; 912 long nativePtr = target.getNativePtr(); 913 if (mTmpValues.type == Float.class || mTmpValues.type == float.class) { 914 if (propertyId < 0) { 915 throw new IllegalArgumentException("Property: " + mTmpValues 916 .propertyName + " is not supported for FullPath"); 917 } 918 propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId, 919 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 920 921 } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) { 922 propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId, 923 (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue); 924 } else { 925 throw new UnsupportedOperationException("Unsupported type: " + 926 mTmpValues.type + ". Only float, int or PathData value is " + 927 "supported for Paths."); 928 } 929 if (mTmpValues.dataSource != null) { 930 float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator 931 .getDuration()); 932 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 933 } 934 createNativeChildAnimator(propertyPtr, startTime, animator); 935 } 936 937 private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values, 938 ObjectAnimator animator, VectorDrawable.VectorDrawableState target, 939 long startTime) { 940 long nativePtr = target.getNativeRenderer(); 941 if (!animator.getPropertyName().equals("alpha")) { 942 throw new UnsupportedOperationException("Only alpha is supported for root " + 943 "group"); 944 } 945 Float startValue = null; 946 Float endValue = null; 947 for (int i = 0; i < values.length; i++) { 948 values[i].getPropertyValues(mTmpValues); 949 if (mTmpValues.propertyName.equals("alpha")) { 950 startValue = (Float) mTmpValues.startValue; 951 endValue = (Float) mTmpValues.endValue; 952 break; 953 } 954 } 955 if (startValue == null && endValue == null) { 956 throw new UnsupportedOperationException("No alpha values are specified"); 957 } 958 long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue); 959 createNativeChildAnimator(propertyPtr, startTime, animator); 960 } 961 962 // These are the data points that define the value of the animating properties. 963 // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1] 964 // a point on the path corresponds to the values of translateX and translateY. 965 // TODO: (Optimization) We should pass the path down in native and chop it into segments 966 // in native. 967 private static float[] createDataPoints( 968 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { 969 long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos(); 970 int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS); 971 int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs); 972 float values[] = new float[numAnimFrames]; 973 float lastFrame = numAnimFrames - 1; 974 for (int i = 0; i < numAnimFrames; i++) { 975 float fraction = i / lastFrame; 976 values[i] = (Float) dataSource.getValueAtFraction(fraction); 977 } 978 return values; 979 } 980 981 private void createNativeChildAnimator(long propertyPtr, long extraDelay, 982 ObjectAnimator animator) { 983 long duration = animator.getDuration(); 984 int repeatCount = animator.getRepeatCount(); 985 long startDelay = extraDelay + animator.getStartDelay(); 986 TimeInterpolator interpolator = animator.getInterpolator(); 987 long nativeInterpolator = 988 RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration); 989 990 startDelay *= ValueAnimator.getDurationScale(); 991 duration *= ValueAnimator.getDurationScale(); 992 993 mStartDelays.add(startDelay); 994 nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration, 995 repeatCount); 996 } 997 998 /** 999 * Holds a weak reference to the target that was last seen (through the DisplayListCanvas 1000 * in the last draw call), so that when animator set needs to start, we can add the animator 1001 * to the last seen RenderNode target and start right away. 1002 */ 1003 protected void recordLastSeenTarget(DisplayListCanvas canvas) { 1004 if (mAnimationPending) { 1005 mLastSeenTarget = new WeakReference<RenderNode>( 1006 RenderNodeAnimatorSetHelper.getTarget(canvas)); 1007 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1008 Log.d(LOGTAG, "Target is set in the next frame"); 1009 } 1010 mAnimationPending = false; 1011 start(); 1012 } else { 1013 mLastSeenTarget = new WeakReference<RenderNode>( 1014 RenderNodeAnimatorSetHelper.getTarget(canvas)); 1015 } 1016 1017 } 1018 1019 private boolean setTarget(RenderNode node) { 1020 if (mTarget != null && mTarget.get() != null) { 1021 // TODO: Maybe we want to support target change. 1022 throw new IllegalStateException("Target already set!"); 1023 } 1024 1025 node.addAnimator(this); 1026 mTarget = new WeakReference<RenderNode>(node); 1027 return true; 1028 } 1029 1030 private boolean useLastSeenTarget() { 1031 if (mLastSeenTarget != null && mLastSeenTarget.get() != null) { 1032 setTarget(mLastSeenTarget.get()); 1033 return true; 1034 } 1035 return false; 1036 } 1037 1038 public void start() { 1039 if (!mInitialized) { 1040 return; 1041 } 1042 1043 if (mStarted) { 1044 return; 1045 } 1046 1047 if (!useLastSeenTarget()) { 1048 mAnimationPending = true; 1049 return; 1050 } 1051 1052 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1053 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java"); 1054 } 1055 1056 nStart(mSetPtr, this); 1057 if (mListener != null) { 1058 mListener.onAnimationStart(null); 1059 } 1060 mStarted = true; 1061 } 1062 1063 public void end() { 1064 if (mInitialized && mStarted) { 1065 nEnd(mSetPtr); 1066 onAnimationEnd(); 1067 } 1068 } 1069 1070 void reset() { 1071 if (!mInitialized) { 1072 return; 1073 } 1074 // TODO: Need to implement reset. 1075 Log.w(LOGTAG, "Reset is yet to be implemented"); 1076 nReset(mSetPtr); 1077 } 1078 1079 // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential 1080 // animators or when the animator set has a start delay 1081 void reverse() { 1082 if (!mIsReversible) { 1083 return; 1084 } 1085 // TODO: Need to support reverse (non-public API) 1086 Log.w(LOGTAG, "Reverse is yet to be implemented"); 1087 nReverse(mSetPtr, this); 1088 } 1089 1090 public long getAnimatorNativePtr() { 1091 return mSetPtr; 1092 } 1093 1094 boolean canReverse() { 1095 return mIsReversible; 1096 } 1097 1098 boolean isStarted() { 1099 return mStarted; 1100 } 1101 1102 boolean isRunning() { 1103 if (!mInitialized) { 1104 return false; 1105 } 1106 return mStarted; 1107 } 1108 1109 void setListener(AnimatorListener listener) { 1110 mListener = listener; 1111 } 1112 1113 void removeListener() { 1114 mListener = null; 1115 } 1116 1117 private void onAnimationEnd() { 1118 mStarted = false; 1119 if (mListener != null) { 1120 mListener.onAnimationEnd(null); 1121 } 1122 mTarget = null; 1123 } 1124 1125 // onFinished: should be called from native 1126 private static void callOnFinished(VectorDrawableAnimator set) { 1127 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1128 Log.d(LOGTAG, "on finished called from native"); 1129 } 1130 set.onAnimationEnd(); 1131 } 1132 } 1133 1134 private static native long nCreateAnimatorSet(); 1135 private static native void nAddAnimator(long setPtr, long propertyValuesHolder, 1136 long nativeInterpolator, long startDelay, long duration, int repeatCount); 1137 1138 private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId, 1139 float startValue, float endValue); 1140 1141 private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr, 1142 long endValuePtr); 1143 private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId, 1144 int startValue, int endValue); 1145 private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId, 1146 float startValue, float endValue); 1147 private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, 1148 float endValue); 1149 private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length); 1150 private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set); 1151 private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set); 1152 private static native void nEnd(long animatorSetPtr); 1153 private static native void nReset(long animatorSetPtr); 1154} 1155