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