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.compat.annotation.UnsupportedAppUsage; 29 import android.content.pm.ActivityInfo.Config; 30 import android.content.res.ColorStateList; 31 import android.content.res.Resources; 32 import android.content.res.Resources.Theme; 33 import android.content.res.TypedArray; 34 import android.graphics.BlendMode; 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.RecordingCanvas; 41 import android.graphics.Rect; 42 import android.graphics.RenderNode; 43 import android.graphics.animation.NativeInterpolatorFactory; 44 import android.os.Build; 45 import android.os.Handler; 46 import android.util.ArrayMap; 47 import android.util.AttributeSet; 48 import android.util.IntArray; 49 import android.util.Log; 50 import android.util.LongArray; 51 import android.util.PathParser; 52 import android.util.Property; 53 import android.util.TimeUtils; 54 import android.view.Choreographer; 55 import android.view.NativeVectorDrawableAnimator; 56 import android.view.View; 57 58 import com.android.internal.R; 59 import com.android.internal.util.VirtualRefBasePtr; 60 61 import dalvik.annotation.optimization.FastNative; 62 63 import org.xmlpull.v1.XmlPullParser; 64 import org.xmlpull.v1.XmlPullParserException; 65 66 import java.io.IOException; 67 import java.lang.ref.WeakReference; 68 import java.util.ArrayList; 69 70 71 /** 72 * This class animates properties of a {@link android.graphics.drawable.VectorDrawable} with 73 * animations defined using {@link android.animation.ObjectAnimator} or 74 * {@link android.animation.AnimatorSet}. 75 * <p> 76 * Starting from API 25, AnimatedVectorDrawable runs on RenderThread (as opposed to on UI thread for 77 * earlier APIs). This means animations in AnimatedVectorDrawable can remain smooth even when there 78 * is heavy workload on the UI thread. Note: If the UI thread is unresponsive, RenderThread may 79 * continue animating until the UI thread is capable of pushing another frame. Therefore, it is not 80 * possible to precisely coordinate a RenderThread-enabled AnimatedVectorDrawable with UI thread 81 * animations. Additionally, 82 * {@link android.graphics.drawable.Animatable2.AnimationCallback#onAnimationEnd(Drawable)} will be 83 * called the frame after the AnimatedVectorDrawable finishes on the RenderThread. 84 * </p> 85 * <p> 86 * AnimatedVectorDrawable can be defined in either <a href="#ThreeXML">three separate XML files</a>, 87 * or <a href="#OneXML">one XML</a>. 88 * </p> 89 * <a name="ThreeXML"></a> 90 * <h3>Define an AnimatedVectorDrawable in three separate XML files</h3> 91 * <ul> 92 * <a name="VDExample"></a> 93 * <li><h4>XML for the VectorDrawable containing properties to be animated</h4> 94 * <p> 95 * Animations can be performed on the animatable attributes in 96 * {@link android.graphics.drawable.VectorDrawable}. These attributes will be animated by 97 * {@link android.animation.ObjectAnimator}. The ObjectAnimator's target can be the root element, 98 * a group element or a path element. The targeted elements need to be named uniquely within 99 * the same VectorDrawable. Elements without animation do not need to be named. 100 * </p> 101 * <p> 102 * Here are all the animatable attributes in {@link android.graphics.drawable.VectorDrawable}: 103 * <table border="2" align="center" cellpadding="5"> 104 * <thead> 105 * <tr> 106 * <th>Element Name</th> 107 * <th>Animatable attribute name</th> 108 * </tr> 109 * </thead> 110 * <tr> 111 * <td><vector></td> 112 * <td>alpha</td> 113 * </tr> 114 * <tr> 115 * <td rowspan="7"><group></td> 116 * <td>rotation</td> 117 * </tr> 118 * <tr> 119 * <td>pivotX</td> 120 * </tr> 121 * <tr> 122 * <td>pivotY</td> 123 * </tr> 124 * <tr> 125 * <td>scaleX</td> 126 * </tr> 127 * <tr> 128 * <td>scaleY</td> 129 * </tr> 130 * <tr> 131 * <td>translateX</td> 132 * </tr> 133 * <tr> 134 * <td>translateY</td> 135 * </tr> 136 * <tr> 137 * <td rowspan="9"><path></td> 138 * <td>pathData</td> 139 * </tr> 140 * <tr> 141 * <td>fillColor</td> 142 * </tr> 143 * <tr> 144 * <td>strokeColor</td> 145 * </tr> 146 * <tr> 147 * <td>strokeWidth</td> 148 * </tr> 149 * <tr> 150 * <td>strokeAlpha</td> 151 * </tr> 152 * <tr> 153 * <td>fillAlpha</td> 154 * </tr> 155 * <tr> 156 * <td>trimPathStart</td> 157 * </tr> 158 * <tr> 159 * <td>trimPathEnd</td> 160 * </tr> 161 * <tr> 162 * <td>trimPathOffset</td> 163 * </tr> 164 * <tr> 165 * <td><clip-path></td> 166 * <td>pathData</td> 167 * </tr> 168 * </table> 169 * </p> 170 * Below is an example of a VectorDrawable defined in vectordrawable.xml. This VectorDrawable is 171 * referred to by its file name (not including file suffix) in the 172 * <a href="#AVDExample">AnimatedVectorDrawable XML example</a>. 173 * <pre> 174 * <vector xmlns:android="http://schemas.android.com/apk/res/android" 175 * android:height="64dp" 176 * android:width="64dp" 177 * android:viewportHeight="600" 178 * android:viewportWidth="600" > 179 * <group 180 * android:name="rotationGroup" 181 * android:pivotX="300.0" 182 * android:pivotY="300.0" 183 * android:rotation="45.0" > 184 * <path 185 * android:name="v" 186 * android:fillColor="#000000" 187 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 188 * </group> 189 * </vector> 190 * </pre></li> 191 * 192 * <a name="AVDExample"></a> 193 * <li><h4>XML for AnimatedVectorDrawable</h4> 194 * <p> 195 * An AnimatedVectorDrawable element has a VectorDrawable attribute, and one or more target 196 * element(s). The target element can specify its target by android:name attribute, and link the 197 * target with the proper ObjectAnimator or AnimatorSet by android:animation attribute. 198 * </p> 199 * The following code sample defines an AnimatedVectorDrawable. Note that the names refer to the 200 * groups and paths in the <a href="#VDExample">VectorDrawable XML above</a>. 201 * <pre> 202 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" 203 * android:drawable="@drawable/vectordrawable" > 204 * <target 205 * android:name="rotationGroup" 206 * android:animation="@animator/rotation" /> 207 * <target 208 * android:name="v" 209 * android:animation="@animator/path_morph" /> 210 * </animated-vector> 211 * </pre> 212 * </li> 213 * 214 * <li><h4>XML for Animations defined using ObjectAnimator or AnimatorSet</h4> 215 * <p> 216 * From the previous <a href="#AVDExample">example of AnimatedVectorDrawable</a>, two animations 217 * were used: rotation.xml and path_morph.xml. 218 * </p> 219 * rotation.xml rotates the target group from 0 degree to 360 degrees over 6000ms: 220 * <pre> 221 * <objectAnimator 222 * android:duration="6000" 223 * android:propertyName="rotation" 224 * android:valueFrom="0" 225 * android:valueTo="360" /> 226 * </pre> 227 * 228 * path_morph.xml morphs the path from one shape into the other. Note that the paths must be 229 * compatible for morphing. Specifically, the paths must have the same commands, in the same order, 230 * and must have the same number of parameters for each command. It is recommended to store path 231 * strings as string resources for reuse. 232 * <pre> 233 * <set xmlns:android="http://schemas.android.com/apk/res/android"> 234 * <objectAnimator 235 * android:duration="3000" 236 * android:propertyName="pathData" 237 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z" 238 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" 239 * android:valueType="pathType"/> 240 * </set> 241 * </pre> 242 * </ul> 243 * <a name="OneXML"></a> 244 * <h3>Define an AnimatedVectorDrawable all in one XML file</h3> 245 * <p> 246 * Since the AAPT tool supports a new format that bundles several related XML files together, we can 247 * merge the XML files from the previous examples into one XML file: 248 * </p> 249 * <pre> 250 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" 251 * xmlns:aapt="http://schemas.android.com/aapt" > 252 * <aapt:attr name="android:drawable"> 253 * <vector 254 * android:height="64dp" 255 * android:width="64dp" 256 * android:viewportHeight="600" 257 * android:viewportWidth="600" > 258 * <group 259 * android:name="rotationGroup" 260 * android:pivotX="300.0" 261 * android:pivotY="300.0" 262 * android:rotation="45.0" > 263 * <path 264 * android:name="v" 265 * android:fillColor="#000000" 266 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 267 * </group> 268 * </vector> 269 * </aapt:attr> 270 * 271 * <target android:name="rotationGroup"> * 272 * <aapt:attr name="android:animation"> 273 * <objectAnimator 274 * android:duration="6000" 275 * android:propertyName="rotation" 276 * android:valueFrom="0" 277 * android:valueTo="360" /> 278 * </aapt:attr> 279 * </target> 280 * 281 * <target android:name="v" > 282 * <aapt:attr name="android:animation"> 283 * <set> 284 * <objectAnimator 285 * android:duration="3000" 286 * android:propertyName="pathData" 287 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z" 288 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" 289 * android:valueType="pathType"/> 290 * </set> 291 * </aapt:attr> 292 * </target> 293 * </animated-vector> 294 * </pre> 295 * 296 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable 297 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name 298 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation 299 */ 300 public class AnimatedVectorDrawable extends Drawable implements Animatable2 { 301 private static final String LOGTAG = "AnimatedVectorDrawable"; 302 303 private static final String ANIMATED_VECTOR = "animated-vector"; 304 private static final String TARGET = "target"; 305 306 private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; 307 308 /** Local, mutable animator set. */ 309 @UnsupportedAppUsage 310 private VectorDrawableAnimator mAnimatorSet; 311 312 /** 313 * The resources against which this drawable was created. Used to attempt 314 * to inflate animators if applyTheme() doesn't get called. 315 */ 316 private Resources mRes; 317 318 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 319 private AnimatedVectorDrawableState mAnimatedVectorState; 320 321 /** The animator set that is parsed from the xml. */ 322 private AnimatorSet mAnimatorSetFromXml = null; 323 324 private boolean mMutated; 325 326 /** Use a internal AnimatorListener to support callbacks during animation events. */ 327 private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null; 328 private AnimatorListener mAnimatorListener = null; 329 AnimatedVectorDrawable()330 public AnimatedVectorDrawable() { 331 this(null, null); 332 } 333 AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res)334 private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) { 335 mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res); 336 mAnimatorSet = new VectorDrawableAnimatorRT(this); 337 mRes = res; 338 } 339 340 @Override mutate()341 public Drawable mutate() { 342 if (!mMutated && super.mutate() == this) { 343 mAnimatedVectorState = new AnimatedVectorDrawableState( 344 mAnimatedVectorState, mCallback, mRes); 345 mMutated = true; 346 } 347 return this; 348 } 349 350 /** 351 * @hide 352 */ clearMutated()353 public void clearMutated() { 354 super.clearMutated(); 355 if (mAnimatedVectorState.mVectorDrawable != null) { 356 mAnimatedVectorState.mVectorDrawable.clearMutated(); 357 } 358 mMutated = false; 359 } 360 361 /** 362 * In order to avoid breaking old apps, we only throw exception on invalid VectorDrawable 363 * animations for apps targeting N and later. For older apps, we ignore (i.e. quietly skip) 364 * these animations. 365 * 366 * @return whether invalid animations for vector drawable should be ignored. 367 */ shouldIgnoreInvalidAnimation()368 private static boolean shouldIgnoreInvalidAnimation() { 369 return android.graphics.Compatibility.getTargetSdkVersion() < Build.VERSION_CODES.N; 370 } 371 372 @Override getConstantState()373 public ConstantState getConstantState() { 374 mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations(); 375 return mAnimatedVectorState; 376 } 377 378 @Override getChangingConfigurations()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 draw(Canvas canvas)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 onBoundsChange(Rect bounds)409 protected void onBoundsChange(Rect bounds) { 410 mAnimatedVectorState.mVectorDrawable.setBounds(bounds); 411 } 412 413 @Override onStateChange(int[] state)414 protected boolean onStateChange(int[] state) { 415 return mAnimatedVectorState.mVectorDrawable.setState(state); 416 } 417 418 @Override onLevelChange(int level)419 protected boolean onLevelChange(int level) { 420 return mAnimatedVectorState.mVectorDrawable.setLevel(level); 421 } 422 423 @Override onLayoutDirectionChanged(@iew.ResolvedLayoutDir int layoutDirection)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 getAlpha()436 public int getAlpha() { 437 return mAnimatedVectorState.mVectorDrawable.getAlpha(); 438 } 439 440 @Override setAlpha(int alpha)441 public void setAlpha(int alpha) { 442 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); 443 } 444 445 @Override setColorFilter(ColorFilter colorFilter)446 public void setColorFilter(ColorFilter colorFilter) { 447 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); 448 } 449 450 @Override getColorFilter()451 public ColorFilter getColorFilter() { 452 return mAnimatedVectorState.mVectorDrawable.getColorFilter(); 453 } 454 455 @Override setTintList(ColorStateList tint)456 public void setTintList(ColorStateList tint) { 457 mAnimatedVectorState.mVectorDrawable.setTintList(tint); 458 } 459 460 @Override setHotspot(float x, float y)461 public void setHotspot(float x, float y) { 462 mAnimatedVectorState.mVectorDrawable.setHotspot(x, y); 463 } 464 465 @Override setHotspotBounds(int left, int top, int right, int bottom)466 public void setHotspotBounds(int left, int top, int right, int bottom) { 467 mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom); 468 } 469 470 @Override setTintBlendMode(@onNull BlendMode blendMode)471 public void setTintBlendMode(@NonNull BlendMode blendMode) { 472 mAnimatedVectorState.mVectorDrawable.setTintBlendMode(blendMode); 473 } 474 475 @Override setVisible(boolean visible, boolean restart)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 isStateful()491 public boolean isStateful() { 492 return mAnimatedVectorState.mVectorDrawable.isStateful(); 493 } 494 495 @Override getOpacity()496 public int getOpacity() { 497 return PixelFormat.TRANSLUCENT; 498 } 499 500 @Override getIntrinsicWidth()501 public int getIntrinsicWidth() { 502 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); 503 } 504 505 @Override getIntrinsicHeight()506 public int getIntrinsicHeight() { 507 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); 508 } 509 510 @Override getOutline(@onNull Outline outline)511 public void getOutline(@NonNull Outline outline) { 512 mAnimatedVectorState.mVectorDrawable.getOutline(outline); 513 } 514 515 @Override getOpticalInsets()516 public Insets getOpticalInsets() { 517 return mAnimatedVectorState.mVectorDrawable.getOpticalInsets(); 518 } 519 520 @Override inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)521 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 522 throws XmlPullParserException, IOException { 523 final AnimatedVectorDrawableState state = mAnimatedVectorState; 524 525 int eventType = parser.getEventType(); 526 float pathErrorScale = 1; 527 final int innerDepth = parser.getDepth() + 1; 528 529 // Parse everything until the end of the animated-vector element. 530 while (eventType != XmlPullParser.END_DOCUMENT 531 && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) { 532 if (eventType == XmlPullParser.START_TAG) { 533 final String tagName = parser.getName(); 534 if (ANIMATED_VECTOR.equals(tagName)) { 535 final TypedArray a = obtainAttributes(res, theme, attrs, 536 R.styleable.AnimatedVectorDrawable); 537 int drawableRes = a.getResourceId( 538 R.styleable.AnimatedVectorDrawable_drawable, 0); 539 if (drawableRes != 0) { 540 VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable( 541 drawableRes, theme).mutate(); 542 vectorDrawable.setAllowCaching(false); 543 vectorDrawable.setCallback(mCallback); 544 pathErrorScale = vectorDrawable.getPixelSize(); 545 if (state.mVectorDrawable != null) { 546 state.mVectorDrawable.setCallback(null); 547 } 548 state.mVectorDrawable = vectorDrawable; 549 } 550 a.recycle(); 551 } else if (TARGET.equals(tagName)) { 552 final TypedArray a = obtainAttributes(res, theme, attrs, 553 R.styleable.AnimatedVectorDrawableTarget); 554 final String target = a.getString( 555 R.styleable.AnimatedVectorDrawableTarget_name); 556 final int animResId = a.getResourceId( 557 R.styleable.AnimatedVectorDrawableTarget_animation, 0); 558 if (animResId != 0) { 559 if (theme != null) { 560 // The animator here could be ObjectAnimator or AnimatorSet. 561 final Animator animator = AnimatorInflater.loadAnimator( 562 res, theme, animResId, pathErrorScale); 563 updateAnimatorProperty(animator, target, state.mVectorDrawable, 564 state.mShouldIgnoreInvalidAnim); 565 state.addTargetAnimator(target, animator); 566 } else { 567 // The animation may be theme-dependent. As a 568 // workaround until Animator has full support for 569 // applyTheme(), postpone loading the animator 570 // until we have a theme in applyTheme(). 571 state.addPendingAnimator(animResId, pathErrorScale, target); 572 573 } 574 } 575 a.recycle(); 576 } 577 } 578 579 eventType = parser.next(); 580 } 581 582 // If we don't have any pending animations, we don't need to hold a 583 // reference to the resources. 584 mRes = state.mPendingAnims == null ? null : res; 585 } 586 updateAnimatorProperty(Animator animator, String targetName, VectorDrawable vectorDrawable, boolean ignoreInvalidAnim)587 private static void updateAnimatorProperty(Animator animator, String targetName, 588 VectorDrawable vectorDrawable, boolean ignoreInvalidAnim) { 589 if (animator instanceof ObjectAnimator) { 590 // Change the property of the Animator from using reflection based on the property 591 // name to a Property object that wraps the setter and getter for modifying that 592 // specific property for a given object. By replacing the reflection with a direct call, 593 // we can largely reduce the time it takes for a animator to modify a VD property. 594 PropertyValuesHolder[] holders = ((ObjectAnimator) animator).getValues(); 595 for (int i = 0; i < holders.length; i++) { 596 PropertyValuesHolder pvh = holders[i]; 597 String propertyName = pvh.getPropertyName(); 598 Object targetNameObj = vectorDrawable.getTargetByName(targetName); 599 Property property = null; 600 if (targetNameObj instanceof VectorDrawable.VObject) { 601 property = ((VectorDrawable.VObject) targetNameObj).getProperty(propertyName); 602 } else if (targetNameObj instanceof VectorDrawable.VectorDrawableState) { 603 property = ((VectorDrawable.VectorDrawableState) targetNameObj) 604 .getProperty(propertyName); 605 } 606 if (property != null) { 607 if (containsSameValueType(pvh, property)) { 608 pvh.setProperty(property); 609 } else if (!ignoreInvalidAnim) { 610 throw new RuntimeException("Wrong valueType for Property: " + propertyName 611 + ". Expected type: " + property.getType().toString() + ". Actual " 612 + "type defined in resources: " + pvh.getValueType().toString()); 613 614 } 615 } 616 } 617 } else if (animator instanceof AnimatorSet) { 618 for (Animator anim : ((AnimatorSet) animator).getChildAnimations()) { 619 updateAnimatorProperty(anim, targetName, vectorDrawable, ignoreInvalidAnim); 620 } 621 } 622 } 623 containsSameValueType(PropertyValuesHolder holder, Property property)624 private static boolean containsSameValueType(PropertyValuesHolder holder, Property property) { 625 Class type1 = holder.getValueType(); 626 Class type2 = property.getType(); 627 if (type1 == float.class || type1 == Float.class) { 628 return type2 == float.class || type2 == Float.class; 629 } else if (type1 == int.class || type1 == Integer.class) { 630 return type2 == int.class || type2 == Integer.class; 631 } else { 632 return type1 == type2; 633 } 634 } 635 636 /** 637 * Force to animate on UI thread. 638 * @hide 639 */ 640 @UnsupportedAppUsage forceAnimationOnUI()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 fallbackOntoUI()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 canApplyTheme()668 public boolean canApplyTheme() { 669 return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme()) 670 || super.canApplyTheme(); 671 } 672 673 @Override applyTheme(Theme t)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 AnimatedVectorDrawableState(AnimatedVectorDrawableState copy, Callback owner, Resources res)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 canApplyTheme()745 public boolean canApplyTheme() { 746 return (mVectorDrawable != null && mVectorDrawable.canApplyTheme()) 747 || mPendingAnims != null || super.canApplyTheme(); 748 } 749 750 @Override newDrawable()751 public Drawable newDrawable() { 752 return new AnimatedVectorDrawable(this, null); 753 } 754 755 @Override newDrawable(Resources res)756 public Drawable newDrawable(Resources res) { 757 return new AnimatedVectorDrawable(this, res); 758 } 759 760 @Override getChangingConfigurations()761 public @Config int getChangingConfigurations() { 762 return mChangingConfigurations; 763 } 764 addPendingAnimator(int resId, float pathErrorScale, String target)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 addTargetAnimator(String targetName, Animator animator)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 */ prepareLocalAnimators(@onNull AnimatorSet animatorSet, @Nullable Resources res)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 */ prepareLocalAnimator(int index)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 */ inflatePendingAnimators(@onNull Resources res, @Nullable Theme t)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 PendingAnimator(int animResId, float pathErrorScale, String target)881 public PendingAnimator(int animResId, float pathErrorScale, String target) { 882 this.animResId = animResId; 883 this.pathErrorScale = pathErrorScale; 884 this.target = target; 885 } 886 newInstance(Resources res, Theme theme)887 public Animator newInstance(Resources res, Theme theme) { 888 return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale); 889 } 890 } 891 } 892 893 @Override isRunning()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 */ reset()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 start()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 ensureAnimatorSet()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 stop()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 */ reverse()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 */ canReverse()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 registerAnimationCallback(@onNull AnimationCallback callback)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. removeAnimatorSetListener()1030 private void removeAnimatorSetListener() { 1031 if (mAnimatorListener != null) { 1032 mAnimatorSet.removeListener(mAnimatorListener); 1033 mAnimatorListener = null; 1034 } 1035 } 1036 1037 @Override unregisterAnimationCallback(@onNull AnimationCallback callback)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 clearAnimationCallbacks()1053 public void clearAnimationCallbacks() { 1054 removeAnimatorSetListener(); 1055 if (mAnimationCallbacks == null) { 1056 return; 1057 } 1058 1059 mAnimationCallbacks.clear(); 1060 } 1061 1062 private interface VectorDrawableAnimator { init(@onNull AnimatorSet set)1063 void init(@NonNull AnimatorSet set); start()1064 void start(); end()1065 void end(); reset()1066 void reset(); reverse()1067 void reverse(); canReverse()1068 boolean canReverse(); setListener(AnimatorListener listener)1069 void setListener(AnimatorListener listener); removeListener(AnimatorListener listener)1070 void removeListener(AnimatorListener listener); onDraw(Canvas canvas)1071 void onDraw(Canvas canvas); isStarted()1072 boolean isStarted(); isRunning()1073 boolean isRunning(); isInfinite()1074 boolean isInfinite(); pause()1075 void pause(); resume()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 VectorDrawableAnimatorUI(@onNull AnimatedVectorDrawable drawable)1089 VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) { 1090 mDrawable = drawable; 1091 } 1092 1093 @Override init(@onNull AnimatorSet set)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 start()1118 public void start() { 1119 if (mSet == null || mSet.isStarted()) { 1120 return; 1121 } 1122 mSet.start(); 1123 invalidateOwningView(); 1124 } 1125 1126 @Override end()1127 public void end() { 1128 if (mSet == null) { 1129 return; 1130 } 1131 mSet.end(); 1132 } 1133 1134 @Override reset()1135 public void reset() { 1136 if (mSet == null) { 1137 return; 1138 } 1139 start(); 1140 mSet.cancel(); 1141 } 1142 1143 @Override reverse()1144 public void reverse() { 1145 if (mSet == null) { 1146 return; 1147 } 1148 mSet.reverse(); 1149 invalidateOwningView(); 1150 } 1151 1152 @Override canReverse()1153 public boolean canReverse() { 1154 return mSet != null && mSet.canReverse(); 1155 } 1156 1157 @Override setListener(AnimatorListener listener)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 removeListener(AnimatorListener listener)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 onDraw(Canvas canvas)1182 public void onDraw(Canvas canvas) { 1183 if (mSet != null && mSet.isStarted()) { 1184 invalidateOwningView(); 1185 } 1186 } 1187 1188 @Override isStarted()1189 public boolean isStarted() { 1190 return mSet != null && mSet.isStarted(); 1191 } 1192 1193 @Override isRunning()1194 public boolean isRunning() { 1195 return mSet != null && mSet.isRunning(); 1196 } 1197 1198 @Override isInfinite()1199 public boolean isInfinite() { 1200 return mIsInfinite; 1201 } 1202 1203 @Override pause()1204 public void pause() { 1205 if (mSet == null) { 1206 return; 1207 } 1208 mSet.pause(); 1209 } 1210 1211 @Override resume()1212 public void resume() { 1213 if (mSet == null) { 1214 return; 1215 } 1216 mSet.resume(); 1217 } 1218 invalidateOwningView()1219 private void invalidateOwningView() { 1220 mDrawable.invalidateSelf(); 1221 } 1222 } 1223 1224 /** 1225 * @hide 1226 */ 1227 public static class VectorDrawableAnimatorRT implements VectorDrawableAnimator, 1228 NativeVectorDrawableAnimator { 1229 private static final int START_ANIMATION = 1; 1230 private static final int REVERSE_ANIMATION = 2; 1231 private static final int RESET_ANIMATION = 3; 1232 private static final int END_ANIMATION = 4; 1233 1234 // If the duration of an animation is more than 300 frames, we cap the sample size to 300. 1235 private static final int MAX_SAMPLE_POINTS = 300; 1236 private Handler mHandler; 1237 private AnimatorListener mListener = null; 1238 private final LongArray mStartDelays = new LongArray(); 1239 private PropertyValuesHolder.PropertyValues mTmpValues = 1240 new PropertyValuesHolder.PropertyValues(); 1241 private long mSetPtr = 0; 1242 private boolean mContainsSequentialAnimators = false; 1243 private boolean mStarted = false; 1244 private boolean mInitialized = false; 1245 private boolean mIsReversible = false; 1246 private boolean mIsInfinite = false; 1247 private final VirtualRefBasePtr mSetRefBasePtr; 1248 private WeakReference<RenderNode> mLastSeenTarget = null; 1249 private int mLastListenerId = 0; 1250 private final IntArray mPendingAnimationActions = new IntArray(); 1251 private final AnimatedVectorDrawable mDrawable; 1252 VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable)1253 VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) { 1254 mDrawable = drawable; 1255 mSetPtr = nCreateAnimatorSet(); 1256 // Increment ref count on native AnimatorSet, so it doesn't get released before Java 1257 // side is done using it. 1258 mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr); 1259 } 1260 1261 @Override init(@onNull AnimatorSet set)1262 public void init(@NonNull AnimatorSet set) { 1263 if (mInitialized) { 1264 // Already initialized 1265 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " + 1266 "re-initialized"); 1267 } 1268 parseAnimatorSet(set, 0); 1269 long vectorDrawableTreePtr = mDrawable.mAnimatedVectorState.mVectorDrawable 1270 .getNativeTree(); 1271 nSetVectorDrawableTarget(mSetPtr, vectorDrawableTreePtr); 1272 mInitialized = true; 1273 mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE; 1274 1275 // Check reversible. 1276 mIsReversible = true; 1277 if (mContainsSequentialAnimators) { 1278 mIsReversible = false; 1279 } else { 1280 // Check if there's any start delay set on child 1281 for (int i = 0; i < mStartDelays.size(); i++) { 1282 if (mStartDelays.get(i) > 0) { 1283 mIsReversible = false; 1284 return; 1285 } 1286 } 1287 } 1288 } 1289 parseAnimatorSet(AnimatorSet set, long startTime)1290 private void parseAnimatorSet(AnimatorSet set, long startTime) { 1291 ArrayList<Animator> animators = set.getChildAnimations(); 1292 1293 boolean playTogether = set.shouldPlayTogether(); 1294 // Convert AnimatorSet to VectorDrawableAnimatorRT 1295 for (int i = 0; i < animators.size(); i++) { 1296 Animator animator = animators.get(i); 1297 // Here we only support ObjectAnimator 1298 if (animator instanceof AnimatorSet) { 1299 parseAnimatorSet((AnimatorSet) animator, startTime); 1300 } else if (animator instanceof ObjectAnimator) { 1301 createRTAnimator((ObjectAnimator) animator, startTime); 1302 } // ignore ValueAnimators and others because they don't directly modify VD 1303 // therefore will be useless to AVD. 1304 1305 if (!playTogether) { 1306 // Assume not play together means play sequentially 1307 startTime += animator.getTotalDuration(); 1308 mContainsSequentialAnimators = true; 1309 } 1310 } 1311 } 1312 1313 // TODO: This method reads animation data from already parsed Animators. We need to move 1314 // this step further up the chain in the parser to avoid the detour. createRTAnimator(ObjectAnimator animator, long startTime)1315 private void createRTAnimator(ObjectAnimator animator, long startTime) { 1316 PropertyValuesHolder[] values = animator.getValues(); 1317 Object target = animator.getTarget(); 1318 if (target instanceof VectorDrawable.VGroup) { 1319 createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target, 1320 startTime); 1321 } else if (target instanceof VectorDrawable.VPath) { 1322 for (int i = 0; i < values.length; i++) { 1323 values[i].getPropertyValues(mTmpValues); 1324 if (mTmpValues.endValue instanceof PathParser.PathData && 1325 mTmpValues.propertyName.equals("pathData")) { 1326 createRTAnimatorForPath(animator, (VectorDrawable.VPath) target, 1327 startTime); 1328 } else if (target instanceof VectorDrawable.VFullPath) { 1329 createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target, 1330 startTime); 1331 } else if (!mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1332 throw new IllegalArgumentException("ClipPath only supports PathData " + 1333 "property"); 1334 } 1335 } 1336 } else if (target instanceof VectorDrawable.VectorDrawableState) { 1337 createRTAnimatorForRootGroup(values, animator, 1338 (VectorDrawable.VectorDrawableState) target, startTime); 1339 } 1340 } 1341 createRTAnimatorForGroup(PropertyValuesHolder[] values, ObjectAnimator animator, VectorDrawable.VGroup target, long startTime)1342 private void createRTAnimatorForGroup(PropertyValuesHolder[] values, 1343 ObjectAnimator animator, VectorDrawable.VGroup target, 1344 long startTime) { 1345 1346 long nativePtr = target.getNativePtr(); 1347 int propertyId; 1348 for (int i = 0; i < values.length; i++) { 1349 // TODO: We need to support the rare case in AVD where no start value is provided 1350 values[i].getPropertyValues(mTmpValues); 1351 propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName); 1352 if (mTmpValues.type != Float.class && mTmpValues.type != float.class) { 1353 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1354 Log.e(LOGTAG, "Unsupported type: " + 1355 mTmpValues.type + ". Only float value is supported for Groups."); 1356 } 1357 continue; 1358 } 1359 if (propertyId < 0) { 1360 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1361 Log.e(LOGTAG, "Unsupported property: " + 1362 mTmpValues.propertyName + " for Vector Drawable Group"); 1363 } 1364 continue; 1365 } 1366 long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId, 1367 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 1368 if (mTmpValues.dataSource != null) { 1369 float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, 1370 animator.getDuration()); 1371 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1372 } 1373 createNativeChildAnimator(propertyPtr, startTime, animator); 1374 } 1375 } createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target, long startTime)1376 private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target, 1377 long startTime) { 1378 1379 long nativePtr = target.getNativePtr(); 1380 long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue) 1381 .getNativePtr(); 1382 long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue) 1383 .getNativePtr(); 1384 long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr, 1385 endPathDataPtr); 1386 createNativeChildAnimator(propertyPtr, startTime, animator); 1387 } 1388 createRTAnimatorForFullPath(ObjectAnimator animator, VectorDrawable.VFullPath target, long startTime)1389 private void createRTAnimatorForFullPath(ObjectAnimator animator, 1390 VectorDrawable.VFullPath target, long startTime) { 1391 1392 int propertyId = target.getPropertyIndex(mTmpValues.propertyName); 1393 long propertyPtr; 1394 long nativePtr = target.getNativePtr(); 1395 if (mTmpValues.type == Float.class || mTmpValues.type == float.class) { 1396 if (propertyId < 0) { 1397 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1398 return; 1399 } else { 1400 throw new IllegalArgumentException("Property: " + mTmpValues.propertyName 1401 + " is not supported for FullPath"); 1402 } 1403 } 1404 propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId, 1405 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 1406 if (mTmpValues.dataSource != null) { 1407 // Pass keyframe data to native, if any. 1408 float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, 1409 animator.getDuration()); 1410 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1411 } 1412 1413 } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) { 1414 propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId, 1415 (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue); 1416 if (mTmpValues.dataSource != null) { 1417 // Pass keyframe data to native, if any. 1418 int[] dataPoints = createIntDataPoints(mTmpValues.dataSource, 1419 animator.getDuration()); 1420 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1421 } 1422 } else { 1423 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1424 return; 1425 } else { 1426 throw new UnsupportedOperationException("Unsupported type: " + 1427 mTmpValues.type + ". Only float, int or PathData value is " + 1428 "supported for Paths."); 1429 } 1430 } 1431 createNativeChildAnimator(propertyPtr, startTime, animator); 1432 } 1433 createRTAnimatorForRootGroup(PropertyValuesHolder[] values, ObjectAnimator animator, VectorDrawable.VectorDrawableState target, long startTime)1434 private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values, 1435 ObjectAnimator animator, VectorDrawable.VectorDrawableState target, 1436 long startTime) { 1437 long nativePtr = target.getNativeRenderer(); 1438 if (!animator.getPropertyName().equals("alpha")) { 1439 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1440 return; 1441 } else { 1442 throw new UnsupportedOperationException("Only alpha is supported for root " 1443 + "group"); 1444 } 1445 } 1446 Float startValue = null; 1447 Float endValue = null; 1448 for (int i = 0; i < values.length; i++) { 1449 values[i].getPropertyValues(mTmpValues); 1450 if (mTmpValues.propertyName.equals("alpha")) { 1451 startValue = (Float) mTmpValues.startValue; 1452 endValue = (Float) mTmpValues.endValue; 1453 break; 1454 } 1455 } 1456 if (startValue == null && endValue == null) { 1457 if (mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) { 1458 return; 1459 } else { 1460 throw new UnsupportedOperationException("No alpha values are specified"); 1461 } 1462 } 1463 long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue); 1464 if (mTmpValues.dataSource != null) { 1465 // Pass keyframe data to native, if any. 1466 float[] dataPoints = createFloatDataPoints(mTmpValues.dataSource, 1467 animator.getDuration()); 1468 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 1469 } 1470 createNativeChildAnimator(propertyPtr, startTime, animator); 1471 } 1472 1473 /** 1474 * Calculate the amount of frames an animation will run based on duration. 1475 */ getFrameCount(long duration)1476 private static int getFrameCount(long duration) { 1477 long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos(); 1478 int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS); 1479 int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs); 1480 // We need 2 frames of data minimum. 1481 numAnimFrames = Math.max(2, numAnimFrames); 1482 if (numAnimFrames > MAX_SAMPLE_POINTS) { 1483 Log.w("AnimatedVectorDrawable", "Duration for the animation is too long :" + 1484 duration + ", the animation will subsample the keyframe or path data."); 1485 numAnimFrames = MAX_SAMPLE_POINTS; 1486 } 1487 return numAnimFrames; 1488 } 1489 1490 // These are the data points that define the value of the animating properties. 1491 // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1] 1492 // a point on the path corresponds to the values of translateX and translateY. 1493 // TODO: (Optimization) We should pass the path down in native and chop it into segments 1494 // in native. createFloatDataPoints( PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration)1495 private static float[] createFloatDataPoints( 1496 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { 1497 int numAnimFrames = getFrameCount(duration); 1498 float values[] = new float[numAnimFrames]; 1499 float lastFrame = numAnimFrames - 1; 1500 for (int i = 0; i < numAnimFrames; i++) { 1501 float fraction = i / lastFrame; 1502 values[i] = (Float) dataSource.getValueAtFraction(fraction); 1503 } 1504 return values; 1505 } 1506 createIntDataPoints( PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration)1507 private static int[] createIntDataPoints( 1508 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { 1509 int numAnimFrames = getFrameCount(duration); 1510 int values[] = new int[numAnimFrames]; 1511 float lastFrame = numAnimFrames - 1; 1512 for (int i = 0; i < numAnimFrames; i++) { 1513 float fraction = i / lastFrame; 1514 values[i] = (Integer) dataSource.getValueAtFraction(fraction); 1515 } 1516 return values; 1517 } 1518 createNativeChildAnimator(long propertyPtr, long extraDelay, ObjectAnimator animator)1519 private void createNativeChildAnimator(long propertyPtr, long extraDelay, 1520 ObjectAnimator animator) { 1521 long duration = animator.getDuration(); 1522 int repeatCount = animator.getRepeatCount(); 1523 long startDelay = extraDelay + animator.getStartDelay(); 1524 TimeInterpolator interpolator = animator.getInterpolator(); 1525 long nativeInterpolator = 1526 NativeInterpolatorFactory.createNativeInterpolator(interpolator, duration); 1527 1528 startDelay *= ValueAnimator.getDurationScale(); 1529 duration *= ValueAnimator.getDurationScale(); 1530 1531 mStartDelays.add(startDelay); 1532 nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration, 1533 repeatCount, animator.getRepeatMode()); 1534 } 1535 1536 /** 1537 * Holds a weak reference to the target that was last seen (through the RecordingCanvas 1538 * in the last draw call), so that when animator set needs to start, we can add the animator 1539 * to the last seen RenderNode target and start right away. 1540 */ recordLastSeenTarget(RecordingCanvas canvas)1541 protected void recordLastSeenTarget(RecordingCanvas canvas) { 1542 final RenderNode node = canvas.mNode; 1543 mLastSeenTarget = new WeakReference<RenderNode>(node); 1544 // Add the animator to the list of animators on every draw 1545 if (mInitialized || mPendingAnimationActions.size() > 0) { 1546 if (useTarget(node)) { 1547 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1548 Log.d(LOGTAG, "Target is set in the next frame"); 1549 } 1550 for (int i = 0; i < mPendingAnimationActions.size(); i++) { 1551 handlePendingAction(mPendingAnimationActions.get(i)); 1552 } 1553 mPendingAnimationActions.clear(); 1554 } 1555 } 1556 } 1557 handlePendingAction(int pendingAnimationAction)1558 private void handlePendingAction(int pendingAnimationAction) { 1559 if (pendingAnimationAction == START_ANIMATION) { 1560 startAnimation(); 1561 } else if (pendingAnimationAction == REVERSE_ANIMATION) { 1562 reverseAnimation(); 1563 } else if (pendingAnimationAction == RESET_ANIMATION) { 1564 resetAnimation(); 1565 } else if (pendingAnimationAction == END_ANIMATION) { 1566 endAnimation(); 1567 } else { 1568 throw new UnsupportedOperationException("Animation action " + 1569 pendingAnimationAction + "is not supported"); 1570 } 1571 } 1572 useLastSeenTarget()1573 private boolean useLastSeenTarget() { 1574 if (mLastSeenTarget != null) { 1575 final RenderNode target = mLastSeenTarget.get(); 1576 return useTarget(target); 1577 } 1578 return false; 1579 } 1580 useTarget(RenderNode target)1581 private boolean useTarget(RenderNode target) { 1582 if (target != null && target.isAttached()) { 1583 target.registerVectorDrawableAnimator(this); 1584 return true; 1585 } 1586 return false; 1587 } 1588 invalidateOwningView()1589 private void invalidateOwningView() { 1590 mDrawable.invalidateSelf(); 1591 } 1592 addPendingAction(int pendingAnimationAction)1593 private void addPendingAction(int pendingAnimationAction) { 1594 invalidateOwningView(); 1595 mPendingAnimationActions.add(pendingAnimationAction); 1596 } 1597 1598 @Override start()1599 public void start() { 1600 if (!mInitialized) { 1601 return; 1602 } 1603 1604 if (useLastSeenTarget()) { 1605 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1606 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java"); 1607 } 1608 startAnimation(); 1609 } else { 1610 addPendingAction(START_ANIMATION); 1611 } 1612 } 1613 1614 @Override end()1615 public void end() { 1616 if (!mInitialized) { 1617 return; 1618 } 1619 1620 if (useLastSeenTarget()) { 1621 endAnimation(); 1622 } else { 1623 addPendingAction(END_ANIMATION); 1624 } 1625 } 1626 1627 @Override reset()1628 public void reset() { 1629 if (!mInitialized) { 1630 return; 1631 } 1632 1633 if (useLastSeenTarget()) { 1634 resetAnimation(); 1635 } else { 1636 addPendingAction(RESET_ANIMATION); 1637 } 1638 } 1639 1640 // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential 1641 // animators or when the animator set has a start delay 1642 @Override reverse()1643 public void reverse() { 1644 if (!mIsReversible || !mInitialized) { 1645 return; 1646 } 1647 if (useLastSeenTarget()) { 1648 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1649 Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java"); 1650 } 1651 reverseAnimation(); 1652 } else { 1653 addPendingAction(REVERSE_ANIMATION); 1654 } 1655 } 1656 1657 // This should only be called after animator has been added to the RenderNode target. startAnimation()1658 private void startAnimation() { 1659 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1660 Log.w(LOGTAG, "starting animation on VD: " + 1661 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 1662 mDrawable.getConstantState()).mVectorDrawable.getConstantState()) 1663 .mRootName); 1664 } 1665 mStarted = true; 1666 if (mHandler == null) { 1667 mHandler = new Handler(); 1668 } 1669 nStart(mSetPtr, this, ++mLastListenerId); 1670 invalidateOwningView(); 1671 if (mListener != null) { 1672 mListener.onAnimationStart(null); 1673 } 1674 } 1675 1676 // This should only be called after animator has been added to the RenderNode target. endAnimation()1677 private void endAnimation() { 1678 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1679 Log.w(LOGTAG, "ending animation on VD: " + 1680 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState) 1681 mDrawable.getConstantState()).mVectorDrawable.getConstantState()) 1682 .mRootName); 1683 } 1684 nEnd(mSetPtr); 1685 invalidateOwningView(); 1686 } 1687 1688 // This should only be called after animator has been added to the RenderNode target. resetAnimation()1689 private void resetAnimation() { 1690 nReset(mSetPtr); 1691 invalidateOwningView(); 1692 } 1693 1694 // This should only be called after animator has been added to the RenderNode target. reverseAnimation()1695 private void reverseAnimation() { 1696 mStarted = true; 1697 nReverse(mSetPtr, this, ++mLastListenerId); 1698 invalidateOwningView(); 1699 if (mListener != null) { 1700 mListener.onAnimationStart(null); 1701 } 1702 } 1703 1704 @Override getAnimatorNativePtr()1705 public long getAnimatorNativePtr() { 1706 return mSetPtr; 1707 } 1708 1709 @Override canReverse()1710 public boolean canReverse() { 1711 return mIsReversible; 1712 } 1713 1714 @Override isStarted()1715 public boolean isStarted() { 1716 return mStarted; 1717 } 1718 1719 @Override isRunning()1720 public boolean isRunning() { 1721 if (!mInitialized) { 1722 return false; 1723 } 1724 return mStarted; 1725 } 1726 1727 @Override setListener(AnimatorListener listener)1728 public void setListener(AnimatorListener listener) { 1729 mListener = listener; 1730 } 1731 1732 @Override removeListener(AnimatorListener listener)1733 public void removeListener(AnimatorListener listener) { 1734 mListener = null; 1735 } 1736 1737 @Override onDraw(Canvas canvas)1738 public void onDraw(Canvas canvas) { 1739 if (canvas.isHardwareAccelerated()) { 1740 recordLastSeenTarget((RecordingCanvas) canvas); 1741 } 1742 } 1743 1744 @Override isInfinite()1745 public boolean isInfinite() { 1746 return mIsInfinite; 1747 } 1748 1749 @Override pause()1750 public void pause() { 1751 // TODO: Implement pause for Animator On RT. 1752 } 1753 1754 @Override resume()1755 public void resume() { 1756 // TODO: Implement resume for Animator On RT. 1757 } 1758 onAnimationEnd(int listenerId)1759 private void onAnimationEnd(int listenerId) { 1760 if (listenerId != mLastListenerId) { 1761 return; 1762 } 1763 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1764 Log.d(LOGTAG, "on finished called from native"); 1765 } 1766 mStarted = false; 1767 // Invalidate in the end of the animation to make sure the data in 1768 // RT thread is synced back to UI thread. 1769 invalidateOwningView(); 1770 if (mListener != null) { 1771 mListener.onAnimationEnd(null); 1772 } 1773 } 1774 1775 // onFinished: should be called from native 1776 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) callOnFinished(VectorDrawableAnimatorRT set, int id)1777 private static void callOnFinished(VectorDrawableAnimatorRT set, int id) { 1778 set.mHandler.post(() -> set.onAnimationEnd(id)); 1779 } 1780 transferPendingActions(VectorDrawableAnimator animatorSet)1781 private void transferPendingActions(VectorDrawableAnimator animatorSet) { 1782 for (int i = 0; i < mPendingAnimationActions.size(); i++) { 1783 int pendingAction = mPendingAnimationActions.get(i); 1784 if (pendingAction == START_ANIMATION) { 1785 animatorSet.start(); 1786 } else if (pendingAction == END_ANIMATION) { 1787 animatorSet.end(); 1788 } else if (pendingAction == REVERSE_ANIMATION) { 1789 animatorSet.reverse(); 1790 } else if (pendingAction == RESET_ANIMATION) { 1791 animatorSet.reset(); 1792 } else { 1793 throw new UnsupportedOperationException("Animation action " + 1794 pendingAction + "is not supported"); 1795 } 1796 } 1797 mPendingAnimationActions.clear(); 1798 } 1799 } 1800 nCreateAnimatorSet()1801 private static native long nCreateAnimatorSet(); nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr)1802 private static native void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr); nAddAnimator(long setPtr, long propertyValuesHolder, long nativeInterpolator, long startDelay, long duration, int repeatCount, int repeatMode)1803 private static native void nAddAnimator(long setPtr, long propertyValuesHolder, 1804 long nativeInterpolator, long startDelay, long duration, int repeatCount, 1805 int repeatMode); nSetPropertyHolderData(long nativePtr, float[] data, int length)1806 private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length); nSetPropertyHolderData(long nativePtr, int[] data, int length)1807 private static native void nSetPropertyHolderData(long nativePtr, int[] data, int length); nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id)1808 private static native void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id); nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id)1809 private static native void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id); 1810 1811 // ------------- @FastNative ------------------- 1812 1813 @FastNative nCreateGroupPropertyHolder(long nativePtr, int propertyId, float startValue, float endValue)1814 private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId, 1815 float startValue, float endValue); 1816 @FastNative nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr, long endValuePtr)1817 private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr, 1818 long endValuePtr); 1819 @FastNative nCreatePathColorPropertyHolder(long nativePtr, int propertyId, int startValue, int endValue)1820 private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId, 1821 int startValue, int endValue); 1822 @FastNative nCreatePathPropertyHolder(long nativePtr, int propertyId, float startValue, float endValue)1823 private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId, 1824 float startValue, float endValue); 1825 @FastNative nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, float endValue)1826 private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, 1827 float endValue); 1828 @FastNative nEnd(long animatorSetPtr)1829 private static native void nEnd(long animatorSetPtr); 1830 @FastNative nReset(long animatorSetPtr)1831 private static native void nReset(long animatorSetPtr); 1832 } 1833