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