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