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