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