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