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