AnimatedVectorDrawable.java revision c21e8bcb013a6a562aa6288143368d9dba80fef4
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(this); 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 } 617 618 @Override 619 public void start() { 620 ensureAnimatorSet(); 621 mAnimatorSet.start(); 622 } 623 624 @NonNull 625 private void ensureAnimatorSet() { 626 if (!mHasAnimatorSet) { 627 // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly 628 // with a list of LocalAnimators. 629 AnimatorSet set = new AnimatorSet(); 630 mAnimatedVectorState.prepareLocalAnimators(set, mRes); 631 mHasAnimatorSet = true; 632 mAnimatorSet.initWithAnimatorSet(set); 633 mRes = null; 634 } 635 } 636 637 @Override 638 public void stop() { 639 mAnimatorSet.end(); 640 } 641 642 /** 643 * Reverses ongoing animations or starts pending animations in reverse. 644 * <p> 645 * NOTE: Only works if all animations support reverse. Otherwise, this will 646 * do nothing. 647 * @hide 648 */ 649 public void reverse() { 650 ensureAnimatorSet(); 651 652 // Only reverse when all the animators can be reversed. 653 if (!canReverse()) { 654 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); 655 return; 656 } 657 658 mAnimatorSet.reverse(); 659 } 660 661 /** 662 * @hide 663 */ 664 public boolean canReverse() { 665 return mAnimatorSet.canReverse(); 666 } 667 668 private final Callback mCallback = new Callback() { 669 @Override 670 public void invalidateDrawable(Drawable who) { 671 invalidateSelf(); 672 } 673 674 @Override 675 public void scheduleDrawable(Drawable who, Runnable what, long when) { 676 scheduleSelf(what, when); 677 } 678 679 @Override 680 public void unscheduleDrawable(Drawable who, Runnable what) { 681 unscheduleSelf(what); 682 } 683 }; 684 685 @Override 686 public void registerAnimationCallback(@NonNull AnimationCallback callback) { 687 if (callback == null) { 688 return; 689 } 690 691 // Add listener accordingly. 692 if (mAnimationCallbacks == null) { 693 mAnimationCallbacks = new ArrayList<>(); 694 } 695 696 mAnimationCallbacks.add(callback); 697 698 if (mAnimatorListener == null) { 699 // Create a animator listener and trigger the callback events when listener is 700 // triggered. 701 mAnimatorListener = new AnimatorListenerAdapter() { 702 @Override 703 public void onAnimationStart(Animator animation) { 704 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks); 705 int size = tmpCallbacks.size(); 706 for (int i = 0; i < size; i ++) { 707 tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawable.this); 708 } 709 } 710 711 @Override 712 public void onAnimationEnd(Animator animation) { 713 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks); 714 int size = tmpCallbacks.size(); 715 for (int i = 0; i < size; i ++) { 716 tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawable.this); 717 } 718 } 719 }; 720 } 721 mAnimatorSet.setListener(mAnimatorListener); 722 } 723 724 // A helper function to clean up the animator listener in the mAnimatorSet. 725 private void removeAnimatorSetListener() { 726 if (mAnimatorListener != null) { 727 mAnimatorSet.removeListener(); 728 mAnimatorListener = null; 729 } 730 } 731 732 @Override 733 public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) { 734 if (mAnimationCallbacks == null || callback == null) { 735 // Nothing to be removed. 736 return false; 737 } 738 boolean removed = mAnimationCallbacks.remove(callback); 739 740 // When the last call back unregistered, remove the listener accordingly. 741 if (mAnimationCallbacks.size() == 0) { 742 removeAnimatorSetListener(); 743 } 744 return removed; 745 } 746 747 @Override 748 public void clearAnimationCallbacks() { 749 removeAnimatorSetListener(); 750 if (mAnimationCallbacks == null) { 751 return; 752 } 753 754 mAnimationCallbacks.clear(); 755 } 756 757 /** 758 * @hide 759 */ 760 public static class VectorDrawableAnimator { 761 private static final int NONE = 0; 762 private static final int START_ANIMATION = 1; 763 private static final int REVERSE_ANIMATION = 2; 764 private AnimatorListener mListener = null; 765 private final LongArray mStartDelays = new LongArray(); 766 private PropertyValuesHolder.PropertyValues mTmpValues = 767 new PropertyValuesHolder.PropertyValues(); 768 private long mSetPtr = 0; 769 private boolean mContainsSequentialAnimators = false; 770 private boolean mStarted = false; 771 private boolean mInitialized = false; 772 private boolean mIsReversible = false; 773 // This needs to be set before parsing starts. 774 private boolean mShouldIgnoreInvalidAnim; 775 // TODO: Consider using NativeAllocationRegistery to track native allocation 776 private final VirtualRefBasePtr mSetRefBasePtr; 777 private WeakReference<RenderNode> mLastSeenTarget = null; 778 private int mLastListenerId = 0; 779 private int mPendingAnimationAction = NONE; 780 private final Drawable mDrawable; 781 782 VectorDrawableAnimator(AnimatedVectorDrawable drawable) { 783 mDrawable = drawable; 784 mSetPtr = nCreateAnimatorSet(); 785 // Increment ref count on native AnimatorSet, so it doesn't get released before Java 786 // side is done using it. 787 mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr); 788 } 789 790 private void initWithAnimatorSet(AnimatorSet set) { 791 if (mInitialized) { 792 // Already initialized 793 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " + 794 "re-initialized"); 795 } 796 mShouldIgnoreInvalidAnim = shouldIgnoreInvalidAnimation(); 797 parseAnimatorSet(set, 0); 798 mInitialized = true; 799 800 // Check reversible. 801 mIsReversible = true; 802 if (mContainsSequentialAnimators) { 803 mIsReversible = false; 804 } else { 805 // Check if there's any start delay set on child 806 for (int i = 0; i < mStartDelays.size(); i++) { 807 if (mStartDelays.get(i) > 0) { 808 mIsReversible = false; 809 return; 810 } 811 } 812 } 813 } 814 815 private void parseAnimatorSet(AnimatorSet set, long startTime) { 816 ArrayList<Animator> animators = set.getChildAnimations(); 817 818 boolean playTogether = set.shouldPlayTogether(); 819 // Convert AnimatorSet to VectorDrawableAnimator 820 for (int i = 0; i < animators.size(); i++) { 821 Animator animator = animators.get(i); 822 // Here we only support ObjectAnimator 823 if (animator instanceof AnimatorSet) { 824 parseAnimatorSet((AnimatorSet) animator, startTime); 825 } else if (animator instanceof ObjectAnimator) { 826 createRTAnimator((ObjectAnimator) animator, startTime); 827 } // ignore ValueAnimators and others because they don't directly modify VD 828 // therefore will be useless to AVD. 829 830 if (!playTogether) { 831 // Assume not play together means play sequentially 832 startTime += animator.getTotalDuration(); 833 mContainsSequentialAnimators = true; 834 } 835 } 836 } 837 838 // TODO: This method reads animation data from already parsed Animators. We need to move 839 // this step further up the chain in the parser to avoid the detour. 840 private void createRTAnimator(ObjectAnimator animator, long startTime) { 841 PropertyValuesHolder[] values = animator.getValues(); 842 Object target = animator.getTarget(); 843 if (target instanceof VectorDrawable.VGroup) { 844 createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target, 845 startTime); 846 } else if (target instanceof VectorDrawable.VPath) { 847 for (int i = 0; i < values.length; i++) { 848 values[i].getPropertyValues(mTmpValues); 849 if (mTmpValues.endValue instanceof PathParser.PathData && 850 mTmpValues.propertyName.equals("pathData")) { 851 createRTAnimatorForPath(animator, (VectorDrawable.VPath) target, 852 startTime); 853 } else if (target instanceof VectorDrawable.VFullPath) { 854 createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target, 855 startTime); 856 } else if (!mShouldIgnoreInvalidAnim) { 857 throw new IllegalArgumentException("ClipPath only supports PathData " + 858 "property"); 859 } 860 861 } 862 } else if (target instanceof VectorDrawable.VectorDrawableState) { 863 createRTAnimatorForRootGroup(values, animator, 864 (VectorDrawable.VectorDrawableState) target, startTime); 865 } else if (!mShouldIgnoreInvalidAnim) { 866 // Should never get here 867 throw new UnsupportedOperationException("Target should be either VGroup, VPath, " + 868 "or ConstantState, " + target == null ? "Null target" : target.getClass() + 869 " is not supported"); 870 } 871 } 872 873 private void createRTAnimatorForGroup(PropertyValuesHolder[] values, 874 ObjectAnimator animator, VectorDrawable.VGroup target, 875 long startTime) { 876 877 long nativePtr = target.getNativePtr(); 878 int propertyId; 879 for (int i = 0; i < values.length; i++) { 880 // TODO: We need to support the rare case in AVD where no start value is provided 881 values[i].getPropertyValues(mTmpValues); 882 propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName); 883 if (mTmpValues.type != Float.class && mTmpValues.type != float.class) { 884 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 885 Log.e(LOGTAG, "Unsupported type: " + 886 mTmpValues.type + ". Only float value is supported for Groups."); 887 } 888 continue; 889 } 890 if (propertyId < 0) { 891 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 892 Log.e(LOGTAG, "Unsupported property: " + 893 mTmpValues.propertyName + " for Vector Drawable Group"); 894 } 895 continue; 896 } 897 long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId, 898 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 899 if (mTmpValues.dataSource != null) { 900 float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator 901 .getDuration()); 902 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 903 } 904 createNativeChildAnimator(propertyPtr, startTime, animator); 905 } 906 } 907 private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target, 908 long startTime) { 909 910 long nativePtr = target.getNativePtr(); 911 long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue) 912 .getNativePtr(); 913 long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue) 914 .getNativePtr(); 915 long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr, 916 endPathDataPtr); 917 createNativeChildAnimator(propertyPtr, startTime, animator); 918 } 919 920 private void createRTAnimatorForFullPath(ObjectAnimator animator, 921 VectorDrawable.VFullPath target, long startTime) { 922 923 int propertyId = target.getPropertyIndex(mTmpValues.propertyName); 924 long propertyPtr; 925 long nativePtr = target.getNativePtr(); 926 if (mTmpValues.type == Float.class || mTmpValues.type == float.class) { 927 if (propertyId < 0) { 928 if (mShouldIgnoreInvalidAnim) { 929 return; 930 } else { 931 throw new IllegalArgumentException("Property: " + mTmpValues.propertyName 932 + " is not supported for FullPath"); 933 } 934 } 935 propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId, 936 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 937 938 } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) { 939 propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId, 940 (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue); 941 } else { 942 if (mShouldIgnoreInvalidAnim) { 943 return; 944 } else { 945 throw new UnsupportedOperationException("Unsupported type: " + 946 mTmpValues.type + ". Only float, int or PathData value is " + 947 "supported for Paths."); 948 } 949 } 950 if (mTmpValues.dataSource != null) { 951 float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator 952 .getDuration()); 953 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 954 } 955 createNativeChildAnimator(propertyPtr, startTime, animator); 956 } 957 958 private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values, 959 ObjectAnimator animator, VectorDrawable.VectorDrawableState target, 960 long startTime) { 961 long nativePtr = target.getNativeRenderer(); 962 if (!animator.getPropertyName().equals("alpha")) { 963 if (mShouldIgnoreInvalidAnim) { 964 return; 965 } else { 966 throw new UnsupportedOperationException("Only alpha is supported for root " 967 + "group"); 968 } 969 } 970 Float startValue = null; 971 Float endValue = null; 972 for (int i = 0; i < values.length; i++) { 973 values[i].getPropertyValues(mTmpValues); 974 if (mTmpValues.propertyName.equals("alpha")) { 975 startValue = (Float) mTmpValues.startValue; 976 endValue = (Float) mTmpValues.endValue; 977 break; 978 } 979 } 980 if (startValue == null && endValue == null) { 981 if (mShouldIgnoreInvalidAnim) { 982 return; 983 } else { 984 throw new UnsupportedOperationException("No alpha values are specified"); 985 } 986 } 987 long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue); 988 createNativeChildAnimator(propertyPtr, startTime, animator); 989 } 990 991 // These are the data points that define the value of the animating properties. 992 // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1] 993 // a point on the path corresponds to the values of translateX and translateY. 994 // TODO: (Optimization) We should pass the path down in native and chop it into segments 995 // in native. 996 private static float[] createDataPoints( 997 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { 998 long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos(); 999 int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS); 1000 int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs); 1001 float values[] = new float[numAnimFrames]; 1002 float lastFrame = numAnimFrames - 1; 1003 for (int i = 0; i < numAnimFrames; i++) { 1004 float fraction = i / lastFrame; 1005 values[i] = (Float) dataSource.getValueAtFraction(fraction); 1006 } 1007 return values; 1008 } 1009 1010 private void createNativeChildAnimator(long propertyPtr, long extraDelay, 1011 ObjectAnimator animator) { 1012 long duration = animator.getDuration(); 1013 int repeatCount = animator.getRepeatCount(); 1014 long startDelay = extraDelay + animator.getStartDelay(); 1015 TimeInterpolator interpolator = animator.getInterpolator(); 1016 long nativeInterpolator = 1017 RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration); 1018 1019 startDelay *= ValueAnimator.getDurationScale(); 1020 duration *= ValueAnimator.getDurationScale(); 1021 1022 mStartDelays.add(startDelay); 1023 nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration, 1024 repeatCount); 1025 } 1026 1027 /** 1028 * Holds a weak reference to the target that was last seen (through the DisplayListCanvas 1029 * in the last draw call), so that when animator set needs to start, we can add the animator 1030 * to the last seen RenderNode target and start right away. 1031 */ 1032 protected void recordLastSeenTarget(DisplayListCanvas canvas) { 1033 mLastSeenTarget = new WeakReference<RenderNode>( 1034 RenderNodeAnimatorSetHelper.getTarget(canvas)); 1035 if (mPendingAnimationAction != NONE) { 1036 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1037 Log.d(LOGTAG, "Target is set in the next frame"); 1038 } 1039 if (mPendingAnimationAction == START_ANIMATION) { 1040 start(); 1041 } else if (mPendingAnimationAction == REVERSE_ANIMATION) { 1042 reverse(); 1043 } 1044 mPendingAnimationAction = NONE; 1045 } 1046 } 1047 1048 private boolean useLastSeenTarget() { 1049 if (mLastSeenTarget != null) { 1050 final RenderNode target = mLastSeenTarget.get(); 1051 if (target != null && target.isAttached()) { 1052 target.addAnimator(this); 1053 return true; 1054 } 1055 } 1056 return false; 1057 } 1058 1059 private void invalidateOwningView() { 1060 mDrawable.invalidateSelf(); 1061 } 1062 1063 public void start() { 1064 if (!mInitialized) { 1065 return; 1066 } 1067 1068 if (!useLastSeenTarget()) { 1069 invalidateOwningView(); 1070 mPendingAnimationAction = START_ANIMATION; 1071 return; 1072 } 1073 1074 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1075 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java"); 1076 } 1077 1078 mStarted = true; 1079 nStart(mSetPtr, this, ++mLastListenerId); 1080 invalidateOwningView(); 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 invalidateOwningView(); 1091 } 1092 } 1093 1094 public void reset() { 1095 if (mInitialized && useLastSeenTarget()) { 1096 // If no target has ever been set, no-op 1097 nReset(mSetPtr); 1098 invalidateOwningView(); 1099 } 1100 } 1101 1102 // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential 1103 // animators or when the animator set has a start delay 1104 void reverse() { 1105 if (!mIsReversible || !mInitialized) { 1106 return; 1107 } 1108 if (!useLastSeenTarget()) { 1109 invalidateOwningView(); 1110 mPendingAnimationAction = REVERSE_ANIMATION; 1111 return; 1112 } 1113 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1114 Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java"); 1115 } 1116 mStarted = true; 1117 nReverse(mSetPtr, this, ++mLastListenerId); 1118 invalidateOwningView(); 1119 if (mListener != null) { 1120 mListener.onAnimationStart(null); 1121 } 1122 } 1123 1124 public long getAnimatorNativePtr() { 1125 return mSetPtr; 1126 } 1127 1128 boolean canReverse() { 1129 return mIsReversible; 1130 } 1131 1132 boolean isStarted() { 1133 return mStarted; 1134 } 1135 1136 boolean isRunning() { 1137 if (!mInitialized) { 1138 return false; 1139 } 1140 return mStarted; 1141 } 1142 1143 void setListener(AnimatorListener listener) { 1144 mListener = listener; 1145 } 1146 1147 void removeListener() { 1148 mListener = null; 1149 } 1150 1151 private void onAnimationEnd(int listenerId) { 1152 if (listenerId != mLastListenerId) { 1153 return; 1154 } 1155 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1156 Log.d(LOGTAG, "on finished called from native"); 1157 } 1158 mStarted = false; 1159 if (mListener != null) { 1160 mListener.onAnimationEnd(null); 1161 } 1162 } 1163 1164 // onFinished: should be called from native 1165 private static void callOnFinished(VectorDrawableAnimator set, int id) { 1166 set.onAnimationEnd(id); 1167 } 1168 } 1169 1170 private static native long nCreateAnimatorSet(); 1171 private static native void nAddAnimator(long setPtr, long propertyValuesHolder, 1172 long nativeInterpolator, long startDelay, long duration, int repeatCount); 1173 1174 private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId, 1175 float startValue, float endValue); 1176 1177 private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr, 1178 long endValuePtr); 1179 private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId, 1180 int startValue, int endValue); 1181 private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId, 1182 float startValue, float endValue); 1183 private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, 1184 float endValue); 1185 private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length); 1186 private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set, int id); 1187 private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set, int id); 1188 private static native void nEnd(long animatorSetPtr); 1189 private static native void nReset(long animatorSetPtr); 1190} 1191