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