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