AnimatedVectorDrawable.java revision 18ad5f43685489e0086dce74f2bfcca9ba39daa3
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15package android.graphics.drawable; 16 17import android.animation.Animator; 18import android.animation.Animator.AnimatorListener; 19import android.animation.AnimatorInflater; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.animation.PropertyValuesHolder; 24import android.animation.TimeInterpolator; 25import android.animation.ValueAnimator; 26import android.annotation.NonNull; 27import android.annotation.Nullable; 28import android.app.ActivityThread; 29import android.app.Application; 30import android.content.pm.ActivityInfo.Config; 31import android.content.res.ColorStateList; 32import android.content.res.Resources; 33import android.content.res.Resources.Theme; 34import android.content.res.TypedArray; 35import android.graphics.Canvas; 36import android.graphics.ColorFilter; 37import android.graphics.Insets; 38import android.graphics.Outline; 39import android.graphics.PixelFormat; 40import android.graphics.PorterDuff; 41import android.graphics.Rect; 42import android.os.Build; 43import android.util.ArrayMap; 44import android.util.AttributeSet; 45import android.util.IntArray; 46import android.util.Log; 47import android.util.LongArray; 48import android.util.PathParser; 49import android.util.Property; 50import android.util.TimeUtils; 51import android.view.Choreographer; 52import android.view.DisplayListCanvas; 53import android.view.RenderNode; 54import android.view.RenderNodeAnimatorSetHelper; 55import android.view.View; 56 57import com.android.internal.R; 58import com.android.internal.util.VirtualRefBasePtr; 59 60import org.xmlpull.v1.XmlPullParser; 61import org.xmlpull.v1.XmlPullParserException; 62 63import java.io.IOException; 64import java.lang.ref.WeakReference; 65import java.util.ArrayList; 66 67import 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 */ 295public 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 localAnimator.setTarget(target); 838 return localAnimator; 839 } 840 841 /** 842 * Inflates pending animators, if any, against a theme. Clears the list of 843 * pending animators. 844 * 845 * @param t the theme against which to inflate the animators 846 */ 847 public void inflatePendingAnimators(@NonNull Resources res, @Nullable Theme t) { 848 final ArrayList<PendingAnimator> pendingAnims = mPendingAnims; 849 if (pendingAnims != null) { 850 mPendingAnims = null; 851 852 for (int i = 0, count = pendingAnims.size(); i < count; i++) { 853 final PendingAnimator pendingAnimator = pendingAnims.get(i); 854 final Animator animator = pendingAnimator.newInstance(res, t); 855 updateAnimatorProperty(animator, pendingAnimator.target, mVectorDrawable, 856 mShouldIgnoreInvalidAnim); 857 addTargetAnimator(pendingAnimator.target, animator); 858 } 859 } 860 } 861 862 /** 863 * Basically a constant state for Animators until we actually implement 864 * constant states for Animators. 865 */ 866 private static class PendingAnimator { 867 public final int animResId; 868 public final float pathErrorScale; 869 public final String target; 870 871 public PendingAnimator(int animResId, float pathErrorScale, String target) { 872 this.animResId = animResId; 873 this.pathErrorScale = pathErrorScale; 874 this.target = target; 875 } 876 877 public Animator newInstance(Resources res, Theme theme) { 878 return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale); 879 } 880 } 881 } 882 883 @Override 884 public boolean isRunning() { 885 return mAnimatorSet.isRunning(); 886 } 887 888 /** 889 * Resets the AnimatedVectorDrawable to the start state as specified in the animators. 890 */ 891 public void reset() { 892 ensureAnimatorSet(); 893 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 894 Log.w(LOGTAG, "calling reset on AVD: " + 895 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 896 getConstantState()).mVectorDrawable.getConstantState()).mRootName 897 + ", at: " + this); 898 } 899 mAnimatorSet.reset(); 900 } 901 902 @Override 903 public void start() { 904 ensureAnimatorSet(); 905 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 906 Log.w(LOGTAG, "calling start on AVD: " + 907 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 908 getConstantState()).mVectorDrawable.getConstantState()).mRootName 909 + ", at: " + this); 910 } 911 mAnimatorSet.start(); 912 } 913 914 @NonNull 915 private void ensureAnimatorSet() { 916 if (mAnimatorSetFromXml == null) { 917 // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly 918 // with a list of LocalAnimators. 919 mAnimatorSetFromXml = new AnimatorSet(); 920 mAnimatedVectorState.prepareLocalAnimators(mAnimatorSetFromXml, mRes); 921 mAnimatorSet.init(mAnimatorSetFromXml); 922 mRes = null; 923 } 924 } 925 926 @Override 927 public void stop() { 928 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 929 Log.w(LOGTAG, "calling stop on AVD: " + 930 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 931 getConstantState()).mVectorDrawable.getConstantState()) 932 .mRootName + ", at: " + this); 933 } 934 mAnimatorSet.end(); 935 } 936 937 /** 938 * Reverses ongoing animations or starts pending animations in reverse. 939 * <p> 940 * NOTE: Only works if all animations support reverse. Otherwise, this will 941 * do nothing. 942 * @hide 943 */ 944 public void reverse() { 945 ensureAnimatorSet(); 946 947 // Only reverse when all the animators can be reversed. 948 if (!canReverse()) { 949 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); 950 return; 951 } 952 953 mAnimatorSet.reverse(); 954 } 955 956 /** 957 * @hide 958 */ 959 public boolean canReverse() { 960 return mAnimatorSet.canReverse(); 961 } 962 963 private final Callback mCallback = new Callback() { 964 @Override 965 public void invalidateDrawable(@NonNull Drawable who) { 966 invalidateSelf(); 967 } 968 969 @Override 970 public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { 971 scheduleSelf(what, when); 972 } 973 974 @Override 975 public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { 976 unscheduleSelf(what); 977 } 978 }; 979 980 @Override 981 public void registerAnimationCallback(@NonNull AnimationCallback callback) { 982 if (callback == null) { 983 return; 984 } 985 986 // Add listener accordingly. 987 if (mAnimationCallbacks == null) { 988 mAnimationCallbacks = new ArrayList<>(); 989 } 990 991 mAnimationCallbacks.add(callback); 992 993 if (mAnimatorListener == null) { 994 // Create a animator listener and trigger the callback events when listener is 995 // triggered. 996 mAnimatorListener = new AnimatorListenerAdapter() { 997 @Override 998 public void onAnimationStart(Animator animation) { 999 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks); 1000 int size = tmpCallbacks.size(); 1001 for (int i = 0; i < size; i ++) { 1002 tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawable.this); 1003 } 1004 } 1005 1006 @Override 1007 public void onAnimationEnd(Animator animation) { 1008 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks); 1009 int size = tmpCallbacks.size(); 1010 for (int i = 0; i < size; i ++) { 1011 tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawable.this); 1012 } 1013 } 1014 }; 1015 } 1016 mAnimatorSet.setListener(mAnimatorListener); 1017 } 1018 1019 // A helper function to clean up the animator listener in the mAnimatorSet. 1020 private void removeAnimatorSetListener() { 1021 if (mAnimatorListener != null) { 1022 mAnimatorSet.removeListener(mAnimatorListener); 1023 mAnimatorListener = null; 1024 } 1025 } 1026 1027 @Override 1028 public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) { 1029 if (mAnimationCallbacks == null || callback == null) { 1030 // Nothing to be removed. 1031 return false; 1032 } 1033 boolean removed = mAnimationCallbacks.remove(callback); 1034 1035 // When the last call back unregistered, remove the listener accordingly. 1036 if (mAnimationCallbacks.size() == 0) { 1037 removeAnimatorSetListener(); 1038 } 1039 return removed; 1040 } 1041 1042 @Override 1043 public void clearAnimationCallbacks() { 1044 removeAnimatorSetListener(); 1045 if (mAnimationCallbacks == null) { 1046 return; 1047 } 1048 1049 mAnimationCallbacks.clear(); 1050 } 1051 1052 private interface VectorDrawableAnimator { 1053 void init(@NonNull AnimatorSet set); 1054 void start(); 1055 void end(); 1056 void reset(); 1057 void reverse(); 1058 boolean canReverse(); 1059 void setListener(AnimatorListener listener); 1060 void removeListener(AnimatorListener listener); 1061 void onDraw(Canvas canvas); 1062 boolean isStarted(); 1063 boolean isRunning(); 1064 boolean isInfinite(); 1065 void pause(); 1066 void resume(); 1067 } 1068 1069 private static class VectorDrawableAnimatorUI implements VectorDrawableAnimator { 1070 // mSet is only initialized in init(). So we need to check whether it is null before any 1071 // operation. 1072 private AnimatorSet mSet = null; 1073 private final Drawable mDrawable; 1074 // Caching the listener in the case when listener operation is called before the mSet is 1075 // setup by init(). 1076 private ArrayList<AnimatorListener> mListenerArray = null; 1077 private boolean mIsInfinite = false; 1078 1079 VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) { 1080 mDrawable = drawable; 1081 } 1082 1083 @Override 1084 public void init(@NonNull AnimatorSet set) { 1085 if (mSet != null) { 1086 // Already initialized 1087 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " + 1088 "re-initialized"); 1089 } 1090 // Keep a deep copy of the set, such that set can be still be constantly representing 1091 // the static content from XML file. 1092 mSet = set.clone(); 1093 mIsInfinite = mSet.getTotalDuration() == Animator.DURATION_INFINITE; 1094 1095 // If there are listeners added before calling init(), now they should be setup. 1096 if (mListenerArray != null && !mListenerArray.isEmpty()) { 1097 for (int i = 0; i < mListenerArray.size(); i++) { 1098 mSet.addListener(mListenerArray.get(i)); 1099 } 1100 mListenerArray.clear(); 1101 mListenerArray = null; 1102 } 1103 } 1104 1105 // Although start(), reset() and reverse() should call init() already, it is better to 1106 // protect these functions from NPE in any situation. 1107 @Override 1108 public void start() { 1109 if (mSet == null || mSet.isStarted()) { 1110 return; 1111 } 1112 mSet.start(); 1113 invalidateOwningView(); 1114 } 1115 1116 @Override 1117 public void end() { 1118 if (mSet == null) { 1119 return; 1120 } 1121 mSet.end(); 1122 } 1123 1124 @Override 1125 public void reset() { 1126 if (mSet == null) { 1127 return; 1128 } 1129 start(); 1130 mSet.cancel(); 1131 } 1132 1133 @Override 1134 public void reverse() { 1135 if (mSet == null) { 1136 return; 1137 } 1138 mSet.reverse(); 1139 invalidateOwningView(); 1140 } 1141 1142 @Override 1143 public boolean canReverse() { 1144 return mSet != null && mSet.canReverse(); 1145 } 1146 1147 @Override 1148 public void setListener(AnimatorListener listener) { 1149 if (mSet == null) { 1150 if (mListenerArray == null) { 1151 mListenerArray = new ArrayList<AnimatorListener>(); 1152 } 1153 mListenerArray.add(listener); 1154 } else { 1155 mSet.addListener(listener); 1156 } 1157 } 1158 1159 @Override 1160 public void removeListener(AnimatorListener listener) { 1161 if (mSet == null) { 1162 if (mListenerArray == null) { 1163 return; 1164 } 1165 mListenerArray.remove(listener); 1166 } else { 1167 mSet.removeListener(listener); 1168 } 1169 } 1170 1171 @Override 1172 public void onDraw(Canvas canvas) { 1173 if (mSet != null && mSet.isStarted()) { 1174 invalidateOwningView(); 1175 } 1176 } 1177 1178 @Override 1179 public boolean isStarted() { 1180 return mSet != null && mSet.isStarted(); 1181 } 1182 1183 @Override 1184 public boolean isRunning() { 1185 return mSet != null && mSet.isRunning(); 1186 } 1187 1188 @Override 1189 public boolean isInfinite() { 1190 return mIsInfinite; 1191 } 1192 1193 @Override 1194 public void pause() { 1195 if (mSet == null) { 1196 return; 1197 } 1198 mSet.pause(); 1199 } 1200 1201 @Override 1202 public void resume() { 1203 if (mSet == null) { 1204 return; 1205 } 1206 mSet.resume(); 1207 } 1208 1209 private void invalidateOwningView() { 1210 mDrawable.invalidateSelf(); 1211 } 1212 } 1213 1214 /** 1215 * @hide 1216 */ 1217 public static class VectorDrawableAnimatorRT implements VectorDrawableAnimator { 1218 private static final int START_ANIMATION = 1; 1219 private static final int REVERSE_ANIMATION = 2; 1220 private static final int RESET_ANIMATION = 3; 1221 private static final int END_ANIMATION = 4; 1222 1223 // If the duration of an animation is more than 300 frames, we cap the sample size to 300. 1224 private static final int MAX_SAMPLE_POINTS = 300; 1225 private AnimatorListener mListener = null; 1226 private final LongArray mStartDelays = new LongArray(); 1227 private PropertyValuesHolder.PropertyValues mTmpValues = 1228 new PropertyValuesHolder.PropertyValues(); 1229 private long mSetPtr = 0; 1230 private boolean mContainsSequentialAnimators = false; 1231 private boolean mStarted = false; 1232 private boolean mInitialized = false; 1233 private boolean mIsReversible = false; 1234 private boolean mIsInfinite = false; 1235 // TODO: Consider using NativeAllocationRegistery to track native allocation 1236 private final VirtualRefBasePtr mSetRefBasePtr; 1237 private WeakReference<RenderNode> mLastSeenTarget = null; 1238 private int mLastListenerId = 0; 1239 private final IntArray mPendingAnimationActions = new IntArray(); 1240 private final AnimatedVectorDrawable mDrawable; 1241 1242 VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) { 1243 mDrawable = drawable; 1244 mSetPtr = nCreateAnimatorSet(); 1245 // Increment ref count on native AnimatorSet, so it doesn't get released before Java 1246 // side is done using it. 1247 mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr); 1248 } 1249 1250 @Override 1251 public void init(@NonNull AnimatorSet set) { 1252 if (mInitialized) { 1253 // Already initialized 1254 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " + 1255 "re-initialized"); 1256 } 1257 parseAnimatorSet(set, 0); 1258 long vectorDrawableTreePtr = mDrawable.mAnimatedVectorState.mVectorDrawable 1259 .getNativeTree(); 1260 nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr); 1261 mInitialized = true; 1262 mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE; 1263 1264 // Check reversible. 1265 mIsReversible = true; 1266 if (mContainsSequentialAnimators) { 1267 mIsReversible = false; 1268 } else { 1269 // Check if there's any start delay set on child 1270 for (int i = 0; i < mStartDelays.size(); i++) { 1271 if (mStartDelays.get(i) > 0) { 1272 mIsReversible = false; 1273 return; 1274 } 1275 } 1276 } 1277 } 1278 1279 private void parseAnimatorSet(AnimatorSet set, long startTime) { 1280 ArrayList<Animator> animators = set.getChildAnimations(); 1281 1282 boolean playTogether = set.shouldPlayTogether(); 1283 // Convert AnimatorSet to VectorDrawableAnimatorRT 1284 for (int i = 0; i < animators.size(); i++) { 1285 Animator animator = animators.get(i); 1286 // Here we only support ObjectAnimator 1287 if (animator instanceof AnimatorSet) { 1288 parseAnimatorSet((AnimatorSet) animator, startTime); 1289 } else if (animator instanceof ObjectAnimator) { 1290 createRTAnimator((ObjectAnimator) animator, startTime); 1291 } // ignore ValueAnimators and others because they don't directly modify VD 1292 // therefore will be useless to AVD. 1293 1294 if (!playTogether) { 1295 // Assume not play together means play sequentially 1296 startTime += animator.getTotalDuration(); 1297 mContainsSequentialAnimators = true; 1298 } 1299 } 1300 } 1301 1302 // TODO: This method reads animation data from already parsed Animators. We need to move 1303 // this step further up the chain in the parser to avoid the detour. 1304 private void createRTAnimator(ObjectAnimator animator, long startTime) { 1305 PropertyValuesHolder[] values = animator.getValues(); 1306 Object target = animator.getTarget(); 1307 if (target instanceof VectorDrawable.VGroup) { 1308 createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target, 1309 startTime); 1310 } else if (target instanceof VectorDrawable.VPath) { 1311 for (int i = 0; i < values.length; i++) { 1312 values[i].getPropertyValues(mTmpValues); 1313 if (mTmpValues.endValue instanceof PathParser.PathData && 1314 mTmpValues.propertyName.equals("pathData")) { 1315 createRTAnimatorForPath(animator, (VectorDrawable.VPath) target, 1316 startTime); 1317 } else if (target instanceof VectorDrawable.VFullPath) { 1318 createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target, 1319 startTime); 1320 } else if (!mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1321 throw new IllegalArgumentException("ClipPath only supports PathData " + 1322 "property"); 1323 } 1324 1325 } 1326 } else if (target instanceof VectorDrawable.VectorDrawableState) { 1327 createRTAnimatorForRootGroup(values, animator, 1328 (VectorDrawable.VectorDrawableState) target, startTime); 1329 } else if (!mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1330 // Should never get here 1331 throw new UnsupportedOperationException("Target should be either VGroup, VPath, " + 1332 "or ConstantState, " + target == null ? "Null target" : target.getClass() + 1333 " is not supported"); 1334 } 1335 } 1336 1337 private void createRTAnimatorForGroup(PropertyValuesHolder[] values, 1338 ObjectAnimator animator, VectorDrawable.VGroup target, 1339 long startTime) { 1340 1341 long nativePtr = target.getNativePtr(); 1342 int propertyId; 1343 for (int i = 0; i < values.length; i++) { 1344 // TODO: We need to support the rare case in AVD where no start value is provided 1345 values[i].getPropertyValues(mTmpValues); 1346 propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName); 1347 if (mTmpValues.type != Float.class && mTmpValues.type != float.class) { 1348 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1349 Log.e(LOGTAG, "Unsupported type: " + 1350 mTmpValues.type + ". Only float value is supported for Groups."); 1351 } 1352 continue; 1353 } 1354 if (propertyId < 0) { 1355 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1356 Log.e(LOGTAG, "Unsupported property: " + 1357 mTmpValues.propertyName + " for Vector Drawable Group"); 1358 } 1359 continue; 1360 } 1361 long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId, 1362 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 1363 if (mTmpValues.dataSource != null) { 1364 float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, 1365 animator.getDuration()); 1366 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1367 } 1368 createNativeChildAnimator(propertyPtr, startTime, animator); 1369 } 1370 } 1371 private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target, 1372 long startTime) { 1373 1374 long nativePtr = target.getNativePtr(); 1375 long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue) 1376 .getNativePtr(); 1377 long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue) 1378 .getNativePtr(); 1379 long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr, 1380 endPathDataPtr); 1381 createNativeChildAnimator(propertyPtr, startTime, animator); 1382 } 1383 1384 private void createRTAnimatorForFullPath(ObjectAnimator animator, 1385 VectorDrawable.VFullPath target, long startTime) { 1386 1387 int propertyId = target.getPropertyIndex(mTmpValues.propertyName); 1388 long propertyPtr; 1389 long nativePtr = target.getNativePtr(); 1390 if (mTmpValues.type == Float.class || mTmpValues.type == float.class) { 1391 if (propertyId < 0) { 1392 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1393 return; 1394 } else { 1395 throw new IllegalArgumentException("Property: " + mTmpValues.propertyName 1396 + " is not supported for FullPath"); 1397 } 1398 } 1399 propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId, 1400 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 1401 if (mTmpValues.dataSource != null) { 1402 // Pass keyframe data to native, if any. 1403 float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, 1404 animator.getDuration()); 1405 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1406 } 1407 1408 } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) { 1409 propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId, 1410 (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue); 1411 if (mTmpValues.dataSource != null) { 1412 // Pass keyframe data to native, if any. 1413 int[] dataPoints = createIntDataPoints(mTmpValues.dataSource, 1414 animator.getDuration()); 1415 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1416 } 1417 } else { 1418 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1419 return; 1420 } else { 1421 throw new UnsupportedOperationException("Unsupported type: " + 1422 mTmpValues.type + ". Only float, int or PathData value is " + 1423 "supported for Paths."); 1424 } 1425 } 1426 createNativeChildAnimator(propertyPtr, startTime, animator); 1427 } 1428 1429 private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values, 1430 ObjectAnimator animator, VectorDrawable.VectorDrawableState target, 1431 long startTime) { 1432 long nativePtr = target.getNativeRenderer(); 1433 if (!animator.getPropertyName().equals("alpha")) { 1434 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1435 return; 1436 } else { 1437 throw new UnsupportedOperationException("Only alpha is supported for root " 1438 + "group"); 1439 } 1440 } 1441 Float startValue = null; 1442 Float endValue = null; 1443 for (int i = 0; i < values.length; i++) { 1444 values[i].getPropertyValues(mTmpValues); 1445 if (mTmpValues.propertyName.equals("alpha")) { 1446 startValue = (Float) mTmpValues.startValue; 1447 endValue = (Float) mTmpValues.endValue; 1448 break; 1449 } 1450 } 1451 if (startValue == null && endValue == null) { 1452 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1453 return; 1454 } else { 1455 throw new UnsupportedOperationException("No alpha values are specified"); 1456 } 1457 } 1458 long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue); 1459 if (mTmpValues.dataSource != null) { 1460 // Pass keyframe data to native, if any. 1461 float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, 1462 animator.getDuration()); 1463 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1464 } 1465 createNativeChildAnimator(propertyPtr, startTime, animator); 1466 } 1467 1468 /** 1469 * Calculate the amount of frames an animation will run based on duration. 1470 */ 1471 private static int getFrameCount(long duration) { 1472 long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos(); 1473 int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS); 1474 int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs); 1475 // We need 2 frames of data minimum. 1476 numAnimFrames = Math.max(2, numAnimFrames); 1477 if (numAnimFrames > MAX_SAMPLE_POINTS) { 1478 Log.w("AnimatedVectorDrawable", "Duration for the animation is too long :" + 1479 duration + ", the animation will subsample the keyframe or path data."); 1480 numAnimFrames = MAX_SAMPLE_POINTS; 1481 } 1482 return numAnimFrames; 1483 } 1484 1485 // These are the data points that define the value of the animating properties. 1486 // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1] 1487 // a point on the path corresponds to the values of translateX and translateY. 1488 // TODO: (Optimization) We should pass the path down in native and chop it into segments 1489 // in native. 1490 private static float[] createFloatDataPoints( 1491 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { 1492 int numAnimFrames = getFrameCount(duration); 1493 float values[] = new float[numAnimFrames]; 1494 float lastFrame = numAnimFrames - 1; 1495 for (int i = 0; i < numAnimFrames; i++) { 1496 float fraction = i / lastFrame; 1497 values[i] = (Float) dataSource.getValueAtFraction(fraction); 1498 } 1499 return values; 1500 } 1501 1502 private static int[] createIntDataPoints( 1503 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { 1504 int numAnimFrames = getFrameCount(duration); 1505 int values[] = new int[numAnimFrames]; 1506 float lastFrame = numAnimFrames - 1; 1507 for (int i = 0; i < numAnimFrames; i++) { 1508 float fraction = i / lastFrame; 1509 values[i] = (Integer) dataSource.getValueAtFraction(fraction); 1510 } 1511 return values; 1512 } 1513 1514 private void createNativeChildAnimator(long propertyPtr, long extraDelay, 1515 ObjectAnimator animator) { 1516 long duration = animator.getDuration(); 1517 int repeatCount = animator.getRepeatCount(); 1518 long startDelay = extraDelay + animator.getStartDelay(); 1519 TimeInterpolator interpolator = animator.getInterpolator(); 1520 long nativeInterpolator = 1521 RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration); 1522 1523 startDelay *= ValueAnimator.getDurationScale(); 1524 duration *= ValueAnimator.getDurationScale(); 1525 1526 mStartDelays.add(startDelay); 1527 nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration, 1528 repeatCount, animator.getRepeatMode()); 1529 } 1530 1531 /** 1532 * Holds a weak reference to the target that was last seen (through the DisplayListCanvas 1533 * in the last draw call), so that when animator set needs to start, we can add the animator 1534 * to the last seen RenderNode target and start right away. 1535 */ 1536 protected void recordLastSeenTarget(DisplayListCanvas canvas) { 1537 final RenderNode node = RenderNodeAnimatorSetHelper.getTarget(canvas); 1538 mLastSeenTarget = new WeakReference<RenderNode>(node); 1539 // Add the animator to the list of animators on every draw 1540 if (mInitialized || mPendingAnimationActions.size() > 0) { 1541 if (useTarget(node)) { 1542 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1543 Log.d(LOGTAG, "Target is set in the next frame"); 1544 } 1545 for (int i = 0; i < mPendingAnimationActions.size(); i++) { 1546 handlePendingAction(mPendingAnimationActions.get(i)); 1547 } 1548 mPendingAnimationActions.clear(); 1549 } 1550 } 1551 } 1552 1553 private void handlePendingAction(int pendingAnimationAction) { 1554 if (pendingAnimationAction == START_ANIMATION) { 1555 startAnimation(); 1556 } else if (pendingAnimationAction == REVERSE_ANIMATION) { 1557 reverseAnimation(); 1558 } else if (pendingAnimationAction == RESET_ANIMATION) { 1559 resetAnimation(); 1560 } else if (pendingAnimationAction == END_ANIMATION) { 1561 endAnimation(); 1562 } else { 1563 throw new UnsupportedOperationException("Animation action " + 1564 pendingAnimationAction + "is not supported"); 1565 } 1566 } 1567 1568 private boolean useLastSeenTarget() { 1569 if (mLastSeenTarget != null) { 1570 final RenderNode target = mLastSeenTarget.get(); 1571 return useTarget(target); 1572 } 1573 return false; 1574 } 1575 1576 private boolean useTarget(RenderNode target) { 1577 if (target != null && target.isAttached()) { 1578 target.registerVectorDrawableAnimator(this); 1579 return true; 1580 } 1581 return false; 1582 } 1583 1584 private void invalidateOwningView() { 1585 mDrawable.invalidateSelf(); 1586 } 1587 1588 private void addPendingAction(int pendingAnimationAction) { 1589 invalidateOwningView(); 1590 mPendingAnimationActions.add(pendingAnimationAction); 1591 } 1592 1593 @Override 1594 public void start() { 1595 if (!mInitialized) { 1596 return; 1597 } 1598 1599 if (useLastSeenTarget()) { 1600 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1601 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java"); 1602 } 1603 startAnimation(); 1604 } else { 1605 addPendingAction(START_ANIMATION); 1606 } 1607 } 1608 1609 @Override 1610 public void end() { 1611 if (!mInitialized) { 1612 return; 1613 } 1614 1615 if (useLastSeenTarget()) { 1616 endAnimation(); 1617 } else { 1618 addPendingAction(END_ANIMATION); 1619 } 1620 } 1621 1622 @Override 1623 public void reset() { 1624 if (!mInitialized) { 1625 return; 1626 } 1627 1628 if (useLastSeenTarget()) { 1629 resetAnimation(); 1630 } else { 1631 addPendingAction(RESET_ANIMATION); 1632 } 1633 } 1634 1635 // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential 1636 // animators or when the animator set has a start delay 1637 @Override 1638 public void reverse() { 1639 if (!mIsReversible || !mInitialized) { 1640 return; 1641 } 1642 if (useLastSeenTarget()) { 1643 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1644 Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java"); 1645 } 1646 reverseAnimation(); 1647 } else { 1648 addPendingAction(REVERSE_ANIMATION); 1649 } 1650 } 1651 1652 // This should only be called after animator has been added to the RenderNode target. 1653 private void startAnimation() { 1654 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1655 Log.w(LOGTAG, "starting animation on VD: " + 1656 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 1657 mDrawable.getConstantState()).mVectorDrawable.getConstantState()) 1658 .mRootName); 1659 } 1660 mStarted = true; 1661 nStart(mSetPtr, this, ++mLastListenerId); 1662 invalidateOwningView(); 1663 if (mListener != null) { 1664 mListener.onAnimationStart(null); 1665 } 1666 } 1667 1668 // This should only be called after animator has been added to the RenderNode target. 1669 private void endAnimation() { 1670 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1671 Log.w(LOGTAG, "ending animation on VD: " + 1672 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 1673 mDrawable.getConstantState()).mVectorDrawable.getConstantState()) 1674 .mRootName); 1675 } 1676 nEnd(mSetPtr); 1677 invalidateOwningView(); 1678 } 1679 1680 // This should only be called after animator has been added to the RenderNode target. 1681 private void resetAnimation() { 1682 nReset(mSetPtr); 1683 invalidateOwningView(); 1684 } 1685 1686 // This should only be called after animator has been added to the RenderNode target. 1687 private void reverseAnimation() { 1688 mStarted = true; 1689 nReverse(mSetPtr, this, ++mLastListenerId); 1690 invalidateOwningView(); 1691 if (mListener != null) { 1692 mListener.onAnimationStart(null); 1693 } 1694 } 1695 1696 public long getAnimatorNativePtr() { 1697 return mSetPtr; 1698 } 1699 1700 @Override 1701 public boolean canReverse() { 1702 return mIsReversible; 1703 } 1704 1705 @Override 1706 public boolean isStarted() { 1707 return mStarted; 1708 } 1709 1710 @Override 1711 public boolean isRunning() { 1712 if (!mInitialized) { 1713 return false; 1714 } 1715 return mStarted; 1716 } 1717 1718 @Override 1719 public void setListener(AnimatorListener listener) { 1720 mListener = listener; 1721 } 1722 1723 @Override 1724 public void removeListener(AnimatorListener listener) { 1725 mListener = null; 1726 } 1727 1728 @Override 1729 public void onDraw(Canvas canvas) { 1730 if (canvas.isHardwareAccelerated()) { 1731 recordLastSeenTarget((DisplayListCanvas) canvas); 1732 } 1733 } 1734 1735 @Override 1736 public boolean isInfinite() { 1737 return mIsInfinite; 1738 } 1739 1740 @Override 1741 public void pause() { 1742 // TODO: Implement pause for Animator On RT. 1743 } 1744 1745 @Override 1746 public void resume() { 1747 // TODO: Implement resume for Animator On RT. 1748 } 1749 1750 private void onAnimationEnd(int listenerId) { 1751 if (listenerId != mLastListenerId) { 1752 return; 1753 } 1754 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1755 Log.d(LOGTAG, "on finished called from native"); 1756 } 1757 mStarted = false; 1758 // Invalidate in the end of the animation to make sure the data in 1759 // RT thread is synced back to UI thread. 1760 invalidateOwningView(); 1761 if (mListener != null) { 1762 mListener.onAnimationEnd(null); 1763 } 1764 } 1765 1766 // onFinished: should be called from native 1767 private static void callOnFinished(VectorDrawableAnimatorRT set, int id) { 1768 set.onAnimationEnd(id); 1769 } 1770 1771 private void transferPendingActions(VectorDrawableAnimator animatorSet) { 1772 for (int i = 0; i < mPendingAnimationActions.size(); i++) { 1773 int pendingAction = mPendingAnimationActions.get(i); 1774 if (pendingAction == START_ANIMATION) { 1775 animatorSet.start(); 1776 } else if (pendingAction == END_ANIMATION) { 1777 animatorSet.end(); 1778 } else if (pendingAction == REVERSE_ANIMATION) { 1779 animatorSet.reverse(); 1780 } else if (pendingAction == RESET_ANIMATION) { 1781 animatorSet.reset(); 1782 } else { 1783 throw new UnsupportedOperationException("Animation action " + 1784 pendingAction + "is not supported"); 1785 } 1786 } 1787 mPendingAnimationActions.clear(); 1788 } 1789 } 1790 1791 private static native long nCreateAnimatorSet(); 1792 private static native void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr); 1793 private static native void nAddAnimator(long setPtr, long propertyValuesHolder, 1794 long nativeInterpolator, long startDelay, long duration, int repeatCount, 1795 int repeatMode); 1796 private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length); 1797 private static native void nSetPropertyHolderData(long nativePtr, int[] data, int length); 1798 private static native void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id); 1799 private static native void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id); 1800 1801 // ------------- @FastNative ------------------- 1802 1803 @FastNative 1804 private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId, 1805 float startValue, float endValue); 1806 @FastNative 1807 private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr, 1808 long endValuePtr); 1809 @FastNative 1810 private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId, 1811 int startValue, int endValue); 1812 @FastNative 1813 private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId, 1814 float startValue, float endValue); 1815 @FastNative 1816 private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, 1817 float endValue); 1818 @FastNative 1819 private static native void nEnd(long animatorSetPtr); 1820 @FastNative 1821 private static native void nReset(long animatorSetPtr); 1822} 1823