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