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