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