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