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