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