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