AnimatedVectorDrawable.java revision 8b083206aef627b6445a8c6be8bf5bb1d778a7f8
1071fc660597efdfa5ebc58b427252393e628a497Jesse Hall/* 2071fc660597efdfa5ebc58b427252393e628a497Jesse Hall * Copyright (C) 2014 The Android Open Source Project 3071fc660597efdfa5ebc58b427252393e628a497Jesse Hall * 4071fc660597efdfa5ebc58b427252393e628a497Jesse Hall * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5071fc660597efdfa5ebc58b427252393e628a497Jesse Hall * in compliance with the License. You may obtain a copy of the License at 6071fc660597efdfa5ebc58b427252393e628a497Jesse Hall * 7071fc660597efdfa5ebc58b427252393e628a497Jesse Hall * http://www.apache.org/licenses/LICENSE-2.0 8071fc660597efdfa5ebc58b427252393e628a497Jesse Hall * 9071fc660597efdfa5ebc58b427252393e628a497Jesse Hall * Unless required by applicable law or agreed to in writing, software distributed under the License 10071fc660597efdfa5ebc58b427252393e628a497Jesse Hall * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11071fc660597efdfa5ebc58b427252393e628a497Jesse Hall * or implied. See the License for the specific language governing permissions and limitations under 12071fc660597efdfa5ebc58b427252393e628a497Jesse Hall * the License. 13071fc660597efdfa5ebc58b427252393e628a497Jesse Hall */ 14071fc660597efdfa5ebc58b427252393e628a497Jesse Hall 15071fc660597efdfa5ebc58b427252393e628a497Jesse Hallpackage android.graphics.drawable; 16071fc660597efdfa5ebc58b427252393e628a497Jesse Hall 17071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.animation.Animator; 18071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.animation.AnimatorInflater; 19071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.animation.AnimatorListenerAdapter; 20071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.animation.AnimatorSet; 21071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.animation.Animator.AnimatorListener; 22071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.animation.PropertyValuesHolder; 23071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.animation.TimeInterpolator; 24071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.animation.ValueAnimator; 25071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.animation.ObjectAnimator; 26071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.annotation.NonNull; 27071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.annotation.Nullable; 28071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.app.ActivityThread; 29071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.app.Application; 30071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.content.res.ColorStateList; 31071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.content.res.Resources; 32071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.content.res.Resources.Theme; 33071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.content.res.TypedArray; 34071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.graphics.Canvas; 35ad0f52fc2be6161f61fd95e4f12739c65661673aDan Albertimport android.graphics.ColorFilter; 36071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.graphics.Insets; 37071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.graphics.Outline; 38071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.graphics.PorterDuff; 39071fc660597efdfa5ebc58b427252393e628a497Jesse Hallimport android.graphics.Rect; 40import android.os.Build; 41import android.util.ArrayMap; 42import android.util.AttributeSet; 43import android.util.Log; 44import android.util.LongArray; 45import android.util.PathParser; 46import android.util.TimeUtils; 47import android.view.Choreographer; 48import android.view.DisplayListCanvas; 49import android.view.RenderNode; 50import android.view.RenderNodeAnimatorSetHelper; 51import android.view.View; 52 53import com.android.internal.R; 54 55import com.android.internal.util.VirtualRefBasePtr; 56import org.xmlpull.v1.XmlPullParser; 57import org.xmlpull.v1.XmlPullParserException; 58 59import java.io.IOException; 60import java.lang.ref.WeakReference; 61import java.util.ArrayList; 62 63/** 64 * This class uses {@link android.animation.ObjectAnimator} and 65 * {@link android.animation.AnimatorSet} to animate the properties of a 66 * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable. 67 * <p> 68 * AnimatedVectorDrawable are normally defined as 3 separate XML files. 69 * </p> 70 * <p> 71 * First is the XML file for {@link android.graphics.drawable.VectorDrawable}. 72 * Note that we allow the animation to happen on the group's attributes and path's 73 * attributes, which requires they are uniquely named in this XML file. Groups 74 * and paths without animations do not need names. 75 * </p> 76 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file. 77 * <pre> 78 * <vector xmlns:android="http://schemas.android.com/apk/res/android" 79 * android:height="64dp" 80 * android:width="64dp" 81 * android:viewportHeight="600" 82 * android:viewportWidth="600" > 83 * <group 84 * android:name="rotationGroup" 85 * android:pivotX="300.0" 86 * android:pivotY="300.0" 87 * android:rotation="45.0" > 88 * <path 89 * android:name="v" 90 * android:fillColor="#000000" 91 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 92 * </group> 93 * </vector> 94 * </pre></li> 95 * <p> 96 * Second is the AnimatedVectorDrawable's XML file, which defines the target 97 * VectorDrawable, the target paths and groups to animate, the properties of the 98 * path and group to animate and the animations defined as the ObjectAnimators 99 * or AnimatorSets. 100 * </p> 101 * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file. 102 * Note how we use the names to refer to the groups and paths in the vectordrawable.xml. 103 * <pre> 104 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" 105 * android:drawable="@drawable/vectordrawable" > 106 * <target 107 * android:name="rotationGroup" 108 * android:animation="@anim/rotation" /> 109 * <target 110 * android:name="v" 111 * android:animation="@anim/path_morph" /> 112 * </animated-vector> 113 * </pre></li> 114 * <p> 115 * Last is the Animator XML file, which is the same as a normal ObjectAnimator 116 * or AnimatorSet. 117 * To complete this example, here are the 2 animator files used in avd.xml: 118 * rotation.xml and path_morph.xml. 119 * </p> 120 * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees. 121 * <pre> 122 * <objectAnimator 123 * android:duration="6000" 124 * android:propertyName="rotation" 125 * android:valueFrom="0" 126 * android:valueTo="360" /> 127 * </pre></li> 128 * <li>Here is the path_morph.xml, which will morph the path from one shape to 129 * the other. Note that the paths must be compatible for morphing. 130 * In more details, the paths should have exact same length of commands , and 131 * exact same length of parameters for each commands. 132 * Note that the path strings are better stored in strings.xml for reusing. 133 * <pre> 134 * <set xmlns:android="http://schemas.android.com/apk/res/android"> 135 * <objectAnimator 136 * android:duration="3000" 137 * android:propertyName="pathData" 138 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z" 139 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" 140 * android:valueType="pathType"/> 141 * </set> 142 * </pre></li> 143 * 144 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable 145 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name 146 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation 147 */ 148public class AnimatedVectorDrawable extends Drawable implements Animatable2 { 149 private static final String LOGTAG = "AnimatedVectorDrawable"; 150 151 private static final String ANIMATED_VECTOR = "animated-vector"; 152 private static final String TARGET = "target"; 153 154 private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; 155 156 /** Local, mutable animator set. */ 157 private final VectorDrawableAnimator mAnimatorSet = new VectorDrawableAnimator(); 158 159 /** 160 * The resources against which this drawable was created. Used to attempt 161 * to inflate animators if applyTheme() doesn't get called. 162 */ 163 private Resources mRes; 164 165 private AnimatedVectorDrawableState mAnimatedVectorState; 166 167 /** Whether the animator set has been prepared. */ 168 private boolean mHasAnimatorSet; 169 170 private boolean mMutated; 171 172 /** Use a internal AnimatorListener to support callbacks during animation events. */ 173 private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null; 174 private AnimatorListener mAnimatorListener = null; 175 176 public AnimatedVectorDrawable() { 177 this(null, null); 178 } 179 180 private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) { 181 mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res); 182 mRes = res; 183 } 184 185 @Override 186 public Drawable mutate() { 187 if (!mMutated && super.mutate() == this) { 188 mAnimatedVectorState = new AnimatedVectorDrawableState( 189 mAnimatedVectorState, mCallback, mRes); 190 mMutated = true; 191 } 192 return this; 193 } 194 195 /** 196 * @hide 197 */ 198 public void clearMutated() { 199 super.clearMutated(); 200 if (mAnimatedVectorState.mVectorDrawable != null) { 201 mAnimatedVectorState.mVectorDrawable.clearMutated(); 202 } 203 mMutated = false; 204 } 205 206 /** 207 * In order to avoid breaking old apps, we only throw exception on invalid VectorDrawable 208 * animations * for apps targeting N and later. For older apps, we ignore (i.e. quietly skip) 209 * these animations. 210 * 211 * @return whether invalid animations for vector drawable should be ignored. 212 */ 213 private static boolean shouldIgnoreInvalidAnimation() { 214 Application app = ActivityThread.currentApplication(); 215 if (app == null || app.getApplicationInfo() == null) { 216 return true; 217 } 218 if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 219 return true; 220 } 221 return false; 222 } 223 224 @Override 225 public ConstantState getConstantState() { 226 mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations(); 227 return mAnimatedVectorState; 228 } 229 230 @Override 231 public int getChangingConfigurations() { 232 return super.getChangingConfigurations() | mAnimatedVectorState.getChangingConfigurations(); 233 } 234 235 @Override 236 public void draw(Canvas canvas) { 237 if (canvas.isHardwareAccelerated()) { 238 mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas); 239 } 240 mAnimatedVectorState.mVectorDrawable.draw(canvas); 241 } 242 243 @Override 244 protected void onBoundsChange(Rect bounds) { 245 mAnimatedVectorState.mVectorDrawable.setBounds(bounds); 246 } 247 248 @Override 249 protected boolean onStateChange(int[] state) { 250 return mAnimatedVectorState.mVectorDrawable.setState(state); 251 } 252 253 @Override 254 protected boolean onLevelChange(int level) { 255 return mAnimatedVectorState.mVectorDrawable.setLevel(level); 256 } 257 258 @Override 259 public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) { 260 return mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection); 261 } 262 263 @Override 264 public int getAlpha() { 265 return mAnimatedVectorState.mVectorDrawable.getAlpha(); 266 } 267 268 @Override 269 public void setAlpha(int alpha) { 270 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); 271 } 272 273 @Override 274 public void setColorFilter(ColorFilter colorFilter) { 275 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); 276 } 277 278 @Override 279 public void setTintList(ColorStateList tint) { 280 mAnimatedVectorState.mVectorDrawable.setTintList(tint); 281 } 282 283 @Override 284 public void setHotspot(float x, float y) { 285 mAnimatedVectorState.mVectorDrawable.setHotspot(x, y); 286 } 287 288 @Override 289 public void setHotspotBounds(int left, int top, int right, int bottom) { 290 mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom); 291 } 292 293 @Override 294 public void setTintMode(PorterDuff.Mode tintMode) { 295 mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode); 296 } 297 298 @Override 299 public boolean setVisible(boolean visible, boolean restart) { 300 mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart); 301 return super.setVisible(visible, restart); 302 } 303 304 @Override 305 public boolean isStateful() { 306 return mAnimatedVectorState.mVectorDrawable.isStateful(); 307 } 308 309 @Override 310 public int getOpacity() { 311 return mAnimatedVectorState.mVectorDrawable.getOpacity(); 312 } 313 314 @Override 315 public int getIntrinsicWidth() { 316 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); 317 } 318 319 @Override 320 public int getIntrinsicHeight() { 321 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); 322 } 323 324 @Override 325 public void getOutline(@NonNull Outline outline) { 326 mAnimatedVectorState.mVectorDrawable.getOutline(outline); 327 } 328 329 /** @hide */ 330 @Override 331 public Insets getOpticalInsets() { 332 return mAnimatedVectorState.mVectorDrawable.getOpticalInsets(); 333 } 334 335 @Override 336 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 337 throws XmlPullParserException, IOException { 338 final AnimatedVectorDrawableState state = mAnimatedVectorState; 339 340 int eventType = parser.getEventType(); 341 float pathErrorScale = 1; 342 while (eventType != XmlPullParser.END_DOCUMENT) { 343 if (eventType == XmlPullParser.START_TAG) { 344 final String tagName = parser.getName(); 345 if (ANIMATED_VECTOR.equals(tagName)) { 346 final TypedArray a = obtainAttributes(res, theme, attrs, 347 R.styleable.AnimatedVectorDrawable); 348 int drawableRes = a.getResourceId( 349 R.styleable.AnimatedVectorDrawable_drawable, 0); 350 if (drawableRes != 0) { 351 VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable( 352 drawableRes, theme).mutate(); 353 vectorDrawable.setAllowCaching(false); 354 vectorDrawable.setCallback(mCallback); 355 pathErrorScale = vectorDrawable.getPixelSize(); 356 if (state.mVectorDrawable != null) { 357 state.mVectorDrawable.setCallback(null); 358 } 359 state.mVectorDrawable = vectorDrawable; 360 } 361 a.recycle(); 362 } else if (TARGET.equals(tagName)) { 363 final TypedArray a = obtainAttributes(res, theme, attrs, 364 R.styleable.AnimatedVectorDrawableTarget); 365 final String target = a.getString( 366 R.styleable.AnimatedVectorDrawableTarget_name); 367 final int animResId = a.getResourceId( 368 R.styleable.AnimatedVectorDrawableTarget_animation, 0); 369 if (animResId != 0) { 370 if (theme != null) { 371 final Animator objectAnimator = AnimatorInflater.loadAnimator( 372 res, theme, animResId, pathErrorScale); 373 state.addTargetAnimator(target, objectAnimator); 374 } else { 375 // The animation may be theme-dependent. As a 376 // workaround until Animator has full support for 377 // applyTheme(), postpone loading the animator 378 // until we have a theme in applyTheme(). 379 state.addPendingAnimator(animResId, pathErrorScale, target); 380 381 } 382 } 383 a.recycle(); 384 } 385 } 386 387 eventType = parser.next(); 388 } 389 390 // If we don't have any pending animations, we don't need to hold a 391 // reference to the resources. 392 mRes = state.mPendingAnims == null ? null : res; 393 } 394 395 @Override 396 public boolean canApplyTheme() { 397 return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme()) 398 || super.canApplyTheme(); 399 } 400 401 @Override 402 public void applyTheme(Theme t) { 403 super.applyTheme(t); 404 405 final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable; 406 if (vectorDrawable != null && vectorDrawable.canApplyTheme()) { 407 vectorDrawable.applyTheme(t); 408 } 409 410 if (t != null) { 411 mAnimatedVectorState.inflatePendingAnimators(t.getResources(), t); 412 } 413 414 // If we don't have any pending animations, we don't need to hold a 415 // reference to the resources. 416 if (mAnimatedVectorState.mPendingAnims == null) { 417 mRes = null; 418 } 419 } 420 421 private static class AnimatedVectorDrawableState extends ConstantState { 422 int mChangingConfigurations; 423 VectorDrawable mVectorDrawable; 424 425 /** Animators that require a theme before inflation. */ 426 ArrayList<PendingAnimator> mPendingAnims; 427 428 /** Fully inflated animators awaiting cloning into an AnimatorSet. */ 429 ArrayList<Animator> mAnimators; 430 431 /** Map of animators to their target object names */ 432 ArrayMap<Animator, String> mTargetNameMap; 433 434 public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy, 435 Callback owner, Resources res) { 436 if (copy != null) { 437 mChangingConfigurations = copy.mChangingConfigurations; 438 439 if (copy.mVectorDrawable != null) { 440 final ConstantState cs = copy.mVectorDrawable.getConstantState(); 441 if (res != null) { 442 mVectorDrawable = (VectorDrawable) cs.newDrawable(res); 443 } else { 444 mVectorDrawable = (VectorDrawable) cs.newDrawable(); 445 } 446 mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate(); 447 mVectorDrawable.setCallback(owner); 448 mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection()); 449 mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds()); 450 mVectorDrawable.setAllowCaching(false); 451 } 452 453 if (copy.mAnimators != null) { 454 mAnimators = new ArrayList<>(copy.mAnimators); 455 } 456 457 if (copy.mTargetNameMap != null) { 458 mTargetNameMap = new ArrayMap<>(copy.mTargetNameMap); 459 } 460 461 if (copy.mPendingAnims != null) { 462 mPendingAnims = new ArrayList<>(copy.mPendingAnims); 463 } 464 } else { 465 mVectorDrawable = new VectorDrawable(); 466 } 467 } 468 469 @Override 470 public boolean canApplyTheme() { 471 return (mVectorDrawable != null && mVectorDrawable.canApplyTheme()) 472 || mPendingAnims != null || super.canApplyTheme(); 473 } 474 475 @Override 476 public Drawable newDrawable() { 477 return new AnimatedVectorDrawable(this, null); 478 } 479 480 @Override 481 public Drawable newDrawable(Resources res) { 482 return new AnimatedVectorDrawable(this, res); 483 } 484 485 @Override 486 public int getChangingConfigurations() { 487 return mChangingConfigurations; 488 } 489 490 public void addPendingAnimator(int resId, float pathErrorScale, String target) { 491 if (mPendingAnims == null) { 492 mPendingAnims = new ArrayList<>(1); 493 } 494 mPendingAnims.add(new PendingAnimator(resId, pathErrorScale, target)); 495 } 496 497 public void addTargetAnimator(String targetName, Animator animator) { 498 if (mAnimators == null) { 499 mAnimators = new ArrayList<>(1); 500 mTargetNameMap = new ArrayMap<>(1); 501 } 502 mAnimators.add(animator); 503 mTargetNameMap.put(animator, targetName); 504 505 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 506 Log.v(LOGTAG, "add animator for target " + targetName + " " + animator); 507 } 508 } 509 510 /** 511 * Prepares a local set of mutable animators based on the constant 512 * state. 513 * <p> 514 * If there are any pending uninflated animators, attempts to inflate 515 * them immediately against the provided resources object. 516 * 517 * @param animatorSet the animator set to which the animators should 518 * be added 519 * @param res the resources against which to inflate any pending 520 * animators, or {@code null} if not available 521 */ 522 public void prepareLocalAnimators(@NonNull AnimatorSet animatorSet, 523 @Nullable Resources res) { 524 // Check for uninflated animators. We can remove this after we add 525 // support for Animator.applyTheme(). See comments in inflate(). 526 if (mPendingAnims != null) { 527 // Attempt to load animators without applying a theme. 528 if (res != null) { 529 inflatePendingAnimators(res, null); 530 } else { 531 Log.e(LOGTAG, "Failed to load animators. Either the AnimatedVectorDrawable" 532 + " must be created using a Resources object or applyTheme() must be" 533 + " called with a non-null Theme object."); 534 } 535 536 mPendingAnims = null; 537 } 538 539 // Perform a deep copy of the constant state's animators. 540 final int count = mAnimators == null ? 0 : mAnimators.size(); 541 if (count > 0) { 542 final Animator firstAnim = prepareLocalAnimator(0); 543 final AnimatorSet.Builder builder = animatorSet.play(firstAnim); 544 for (int i = 1; i < count; ++i) { 545 final Animator nextAnim = prepareLocalAnimator(i); 546 builder.with(nextAnim); 547 } 548 } 549 } 550 551 /** 552 * Prepares a local animator for the given index within the constant 553 * state's list of animators. 554 * 555 * @param index the index of the animator within the constant state 556 */ 557 private Animator prepareLocalAnimator(int index) { 558 final Animator animator = mAnimators.get(index); 559 final Animator localAnimator = animator.clone(); 560 final String targetName = mTargetNameMap.get(animator); 561 final Object target = mVectorDrawable.getTargetByName(targetName); 562 localAnimator.setTarget(target); 563 return localAnimator; 564 } 565 566 /** 567 * Inflates pending animators, if any, against a theme. Clears the list of 568 * pending animators. 569 * 570 * @param t the theme against which to inflate the animators 571 */ 572 public void inflatePendingAnimators(@NonNull Resources res, @Nullable Theme t) { 573 final ArrayList<PendingAnimator> pendingAnims = mPendingAnims; 574 if (pendingAnims != null) { 575 mPendingAnims = null; 576 577 for (int i = 0, count = pendingAnims.size(); i < count; i++) { 578 final PendingAnimator pendingAnimator = pendingAnims.get(i); 579 final Animator objectAnimator = pendingAnimator.newInstance(res, t); 580 addTargetAnimator(pendingAnimator.target, objectAnimator); 581 } 582 } 583 } 584 585 /** 586 * Basically a constant state for Animators until we actually implement 587 * constant states for Animators. 588 */ 589 private static class PendingAnimator { 590 public final int animResId; 591 public final float pathErrorScale; 592 public final String target; 593 594 public PendingAnimator(int animResId, float pathErrorScale, String target) { 595 this.animResId = animResId; 596 this.pathErrorScale = pathErrorScale; 597 this.target = target; 598 } 599 600 public Animator newInstance(Resources res, Theme theme) { 601 return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale); 602 } 603 } 604 } 605 606 @Override 607 public boolean isRunning() { 608 return mAnimatorSet.isRunning(); 609 } 610 611 /** 612 * Resets the AnimatedVectorDrawable to the start state as specified in the animators. 613 */ 614 public void reset() { 615 mAnimatorSet.reset(); 616 invalidateSelf(); 617 } 618 619 @Override 620 public void start() { 621 ensureAnimatorSet(); 622 mAnimatorSet.start(); 623 invalidateSelf(); 624 } 625 626 @NonNull 627 private void ensureAnimatorSet() { 628 if (!mHasAnimatorSet) { 629 // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly 630 // with a list of LocalAnimators. 631 AnimatorSet set = new AnimatorSet(); 632 mAnimatedVectorState.prepareLocalAnimators(set, mRes); 633 mHasAnimatorSet = true; 634 mAnimatorSet.initWithAnimatorSet(set); 635 mRes = null; 636 } 637 } 638 639 @Override 640 public void stop() { 641 mAnimatorSet.end(); 642 invalidateSelf(); 643 } 644 645 /** 646 * Reverses ongoing animations or starts pending animations in reverse. 647 * <p> 648 * NOTE: Only works if all animations support reverse. Otherwise, this will 649 * do nothing. 650 * @hide 651 */ 652 public void reverse() { 653 ensureAnimatorSet(); 654 655 // Only reverse when all the animators can be reversed. 656 if (!canReverse()) { 657 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); 658 return; 659 } 660 661 mAnimatorSet.reverse(); 662 invalidateSelf(); 663 } 664 665 /** 666 * @hide 667 */ 668 public boolean canReverse() { 669 return mAnimatorSet.canReverse(); 670 } 671 672 private final Callback mCallback = new Callback() { 673 @Override 674 public void invalidateDrawable(Drawable who) { 675 invalidateSelf(); 676 } 677 678 @Override 679 public void scheduleDrawable(Drawable who, Runnable what, long when) { 680 scheduleSelf(what, when); 681 } 682 683 @Override 684 public void unscheduleDrawable(Drawable who, Runnable what) { 685 unscheduleSelf(what); 686 } 687 }; 688 689 @Override 690 public void registerAnimationCallback(@NonNull AnimationCallback callback) { 691 if (callback == null) { 692 return; 693 } 694 695 // Add listener accordingly. 696 if (mAnimationCallbacks == null) { 697 mAnimationCallbacks = new ArrayList<>(); 698 } 699 700 mAnimationCallbacks.add(callback); 701 702 if (mAnimatorListener == null) { 703 // Create a animator listener and trigger the callback events when listener is 704 // triggered. 705 mAnimatorListener = new AnimatorListenerAdapter() { 706 @Override 707 public void onAnimationStart(Animator animation) { 708 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks); 709 int size = tmpCallbacks.size(); 710 for (int i = 0; i < size; i ++) { 711 tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawable.this); 712 } 713 } 714 715 @Override 716 public void onAnimationEnd(Animator animation) { 717 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks); 718 int size = tmpCallbacks.size(); 719 for (int i = 0; i < size; i ++) { 720 tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawable.this); 721 } 722 } 723 }; 724 } 725 mAnimatorSet.setListener(mAnimatorListener); 726 } 727 728 // A helper function to clean up the animator listener in the mAnimatorSet. 729 private void removeAnimatorSetListener() { 730 if (mAnimatorListener != null) { 731 mAnimatorSet.removeListener(); 732 mAnimatorListener = null; 733 } 734 } 735 736 @Override 737 public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) { 738 if (mAnimationCallbacks == null || callback == null) { 739 // Nothing to be removed. 740 return false; 741 } 742 boolean removed = mAnimationCallbacks.remove(callback); 743 744 // When the last call back unregistered, remove the listener accordingly. 745 if (mAnimationCallbacks.size() == 0) { 746 removeAnimatorSetListener(); 747 } 748 return removed; 749 } 750 751 @Override 752 public void clearAnimationCallbacks() { 753 removeAnimatorSetListener(); 754 if (mAnimationCallbacks == null) { 755 return; 756 } 757 758 mAnimationCallbacks.clear(); 759 } 760 761 /** 762 * @hide 763 */ 764 public static class VectorDrawableAnimator { 765 private static final int NONE = 0; 766 private static final int START_ANIMATION = 1; 767 private static final int REVERSE_ANIMATION = 2; 768 private AnimatorListener mListener = null; 769 private final LongArray mStartDelays = new LongArray(); 770 private PropertyValuesHolder.PropertyValues mTmpValues = 771 new PropertyValuesHolder.PropertyValues(); 772 private long mSetPtr = 0; 773 private boolean mContainsSequentialAnimators = false; 774 private boolean mStarted = false; 775 private boolean mInitialized = false; 776 private boolean mIsReversible = false; 777 // This needs to be set before parsing starts. 778 private boolean mShouldIgnoreInvalidAnim; 779 // TODO: Consider using NativeAllocationRegistery to track native allocation 780 private final VirtualRefBasePtr mSetRefBasePtr; 781 private WeakReference<RenderNode> mLastSeenTarget = null; 782 private int mLastListenerId = 0; 783 private int mPendingAnimationAction = NONE; 784 785 VectorDrawableAnimator() { 786 mSetPtr = nCreateAnimatorSet(); 787 // Increment ref count on native AnimatorSet, so it doesn't get released before Java 788 // side is done using it. 789 mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr); 790 } 791 792 private void initWithAnimatorSet(AnimatorSet set) { 793 if (mInitialized) { 794 // Already initialized 795 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " + 796 "re-initialized"); 797 } 798 mShouldIgnoreInvalidAnim = shouldIgnoreInvalidAnimation(); 799 parseAnimatorSet(set, 0); 800 mInitialized = true; 801 802 // Check reversible. 803 mIsReversible = true; 804 if (mContainsSequentialAnimators) { 805 mIsReversible = false; 806 } else { 807 // Check if there's any start delay set on child 808 for (int i = 0; i < mStartDelays.size(); i++) { 809 if (mStartDelays.get(i) > 0) { 810 mIsReversible = false; 811 return; 812 } 813 } 814 } 815 } 816 817 private void parseAnimatorSet(AnimatorSet set, long startTime) { 818 ArrayList<Animator> animators = set.getChildAnimations(); 819 820 boolean playTogether = set.shouldPlayTogether(); 821 // Convert AnimatorSet to VectorDrawableAnimator 822 for (int i = 0; i < animators.size(); i++) { 823 Animator animator = animators.get(i); 824 // Here we only support ObjectAnimator 825 if (animator instanceof AnimatorSet) { 826 parseAnimatorSet((AnimatorSet) animator, startTime); 827 } else if (animator instanceof ObjectAnimator) { 828 createRTAnimator((ObjectAnimator) animator, startTime); 829 } // ignore ValueAnimators and others because they don't directly modify VD 830 // therefore will be useless to AVD. 831 832 if (!playTogether) { 833 // Assume not play together means play sequentially 834 startTime += animator.getTotalDuration(); 835 mContainsSequentialAnimators = true; 836 } 837 } 838 } 839 840 // TODO: This method reads animation data from already parsed Animators. We need to move 841 // this step further up the chain in the parser to avoid the detour. 842 private void createRTAnimator(ObjectAnimator animator, long startTime) { 843 PropertyValuesHolder[] values = animator.getValues(); 844 Object target = animator.getTarget(); 845 if (target instanceof VectorDrawable.VGroup) { 846 createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target, 847 startTime); 848 } else if (target instanceof VectorDrawable.VPath) { 849 for (int i = 0; i < values.length; i++) { 850 values[i].getPropertyValues(mTmpValues); 851 if (mTmpValues.endValue instanceof PathParser.PathData && 852 mTmpValues.propertyName.equals("pathData")) { 853 createRTAnimatorForPath(animator, (VectorDrawable.VPath) target, 854 startTime); 855 } else if (target instanceof VectorDrawable.VFullPath) { 856 createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target, 857 startTime); 858 } else if (!mShouldIgnoreInvalidAnim) { 859 throw new IllegalArgumentException("ClipPath only supports PathData " + 860 "property"); 861 } 862 863 } 864 } else if (target instanceof VectorDrawable.VectorDrawableState) { 865 createRTAnimatorForRootGroup(values, animator, 866 (VectorDrawable.VectorDrawableState) target, startTime); 867 } else if (!mShouldIgnoreInvalidAnim) { 868 // Should never get here 869 throw new UnsupportedOperationException("Target should be either VGroup, VPath, " + 870 "or ConstantState, " + target == null ? "Null target" : target.getClass() + 871 " is not supported"); 872 } 873 } 874 875 private void createRTAnimatorForGroup(PropertyValuesHolder[] values, 876 ObjectAnimator animator, VectorDrawable.VGroup target, 877 long startTime) { 878 879 long nativePtr = target.getNativePtr(); 880 int propertyId; 881 for (int i = 0; i < values.length; i++) { 882 // TODO: We need to support the rare case in AVD where no start value is provided 883 values[i].getPropertyValues(mTmpValues); 884 propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName); 885 if (mTmpValues.type != Float.class && mTmpValues.type != float.class) { 886 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 887 Log.e(LOGTAG, "Unsupported type: " + 888 mTmpValues.type + ". Only float value is supported for Groups."); 889 } 890 continue; 891 } 892 if (propertyId < 0) { 893 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 894 Log.e(LOGTAG, "Unsupported property: " + 895 mTmpValues.propertyName + " for Vector Drawable Group"); 896 } 897 continue; 898 } 899 long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId, 900 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 901 if (mTmpValues.dataSource != null) { 902 float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator 903 .getDuration()); 904 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 905 } 906 createNativeChildAnimator(propertyPtr, startTime, animator); 907 } 908 } 909 private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target, 910 long startTime) { 911 912 long nativePtr = target.getNativePtr(); 913 long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue) 914 .getNativePtr(); 915 long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue) 916 .getNativePtr(); 917 long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr, 918 endPathDataPtr); 919 createNativeChildAnimator(propertyPtr, startTime, animator); 920 } 921 922 private void createRTAnimatorForFullPath(ObjectAnimator animator, 923 VectorDrawable.VFullPath target, long startTime) { 924 925 int propertyId = target.getPropertyIndex(mTmpValues.propertyName); 926 long propertyPtr; 927 long nativePtr = target.getNativePtr(); 928 if (mTmpValues.type == Float.class || mTmpValues.type == float.class) { 929 if (propertyId < 0) { 930 if (mShouldIgnoreInvalidAnim) { 931 return; 932 } else { 933 throw new IllegalArgumentException("Property: " + mTmpValues.propertyName 934 + " is not supported for FullPath"); 935 } 936 } 937 propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId, 938 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); 939 940 } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) { 941 propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId, 942 (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue); 943 } else { 944 if (mShouldIgnoreInvalidAnim) { 945 return; 946 } else { 947 throw new UnsupportedOperationException("Unsupported type: " + 948 mTmpValues.type + ". Only float, int or PathData value is " + 949 "supported for Paths."); 950 } 951 } 952 if (mTmpValues.dataSource != null) { 953 float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator 954 .getDuration()); 955 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); 956 } 957 createNativeChildAnimator(propertyPtr, startTime, animator); 958 } 959 960 private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values, 961 ObjectAnimator animator, VectorDrawable.VectorDrawableState target, 962 long startTime) { 963 long nativePtr = target.getNativeRenderer(); 964 if (!animator.getPropertyName().equals("alpha")) { 965 if (mShouldIgnoreInvalidAnim) { 966 return; 967 } else { 968 throw new UnsupportedOperationException("Only alpha is supported for root " 969 + "group"); 970 } 971 } 972 Float startValue = null; 973 Float endValue = null; 974 for (int i = 0; i < values.length; i++) { 975 values[i].getPropertyValues(mTmpValues); 976 if (mTmpValues.propertyName.equals("alpha")) { 977 startValue = (Float) mTmpValues.startValue; 978 endValue = (Float) mTmpValues.endValue; 979 break; 980 } 981 } 982 if (startValue == null && endValue == null) { 983 if (mShouldIgnoreInvalidAnim) { 984 return; 985 } else { 986 throw new UnsupportedOperationException("No alpha values are specified"); 987 } 988 } 989 long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue); 990 createNativeChildAnimator(propertyPtr, startTime, animator); 991 } 992 993 // These are the data points that define the value of the animating properties. 994 // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1] 995 // a point on the path corresponds to the values of translateX and translateY. 996 // TODO: (Optimization) We should pass the path down in native and chop it into segments 997 // in native. 998 private static float[] createDataPoints( 999 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { 1000 long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos(); 1001 int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS); 1002 int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs); 1003 float values[] = new float[numAnimFrames]; 1004 float lastFrame = numAnimFrames - 1; 1005 for (int i = 0; i < numAnimFrames; i++) { 1006 float fraction = i / lastFrame; 1007 values[i] = (Float) dataSource.getValueAtFraction(fraction); 1008 } 1009 return values; 1010 } 1011 1012 private void createNativeChildAnimator(long propertyPtr, long extraDelay, 1013 ObjectAnimator animator) { 1014 long duration = animator.getDuration(); 1015 int repeatCount = animator.getRepeatCount(); 1016 long startDelay = extraDelay + animator.getStartDelay(); 1017 TimeInterpolator interpolator = animator.getInterpolator(); 1018 long nativeInterpolator = 1019 RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration); 1020 1021 startDelay *= ValueAnimator.getDurationScale(); 1022 duration *= ValueAnimator.getDurationScale(); 1023 1024 mStartDelays.add(startDelay); 1025 nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration, 1026 repeatCount); 1027 } 1028 1029 /** 1030 * Holds a weak reference to the target that was last seen (through the DisplayListCanvas 1031 * in the last draw call), so that when animator set needs to start, we can add the animator 1032 * to the last seen RenderNode target and start right away. 1033 */ 1034 protected void recordLastSeenTarget(DisplayListCanvas canvas) { 1035 mLastSeenTarget = new WeakReference<RenderNode>( 1036 RenderNodeAnimatorSetHelper.getTarget(canvas)); 1037 if (mPendingAnimationAction != NONE) { 1038 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1039 Log.d(LOGTAG, "Target is set in the next frame"); 1040 } 1041 if (mPendingAnimationAction == START_ANIMATION) { 1042 start(); 1043 } else if (mPendingAnimationAction == REVERSE_ANIMATION) { 1044 reverse(); 1045 } 1046 mPendingAnimationAction = NONE; 1047 } 1048 } 1049 1050 private boolean useLastSeenTarget() { 1051 if (mLastSeenTarget != null) { 1052 final RenderNode target = mLastSeenTarget.get(); 1053 if (target != null && target.isAttached()) { 1054 target.addAnimator(this); 1055 return true; 1056 } 1057 } 1058 return false; 1059 } 1060 1061 public void start() { 1062 if (!mInitialized) { 1063 return; 1064 } 1065 1066 if (!useLastSeenTarget()) { 1067 mPendingAnimationAction = START_ANIMATION; 1068 return; 1069 } 1070 1071 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1072 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java"); 1073 } 1074 1075 mStarted = true; 1076 nStart(mSetPtr, this, ++mLastListenerId); 1077 if (mListener != null) { 1078 mListener.onAnimationStart(null); 1079 } 1080 } 1081 1082 public void end() { 1083 if (mInitialized && useLastSeenTarget()) { 1084 // If no target has ever been set, no-op 1085 nEnd(mSetPtr); 1086 } 1087 } 1088 1089 public void reset() { 1090 if (mInitialized && useLastSeenTarget()) { 1091 // If no target has ever been set, no-op 1092 nReset(mSetPtr); 1093 } 1094 } 1095 1096 // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential 1097 // animators or when the animator set has a start delay 1098 void reverse() { 1099 if (!mIsReversible || !mInitialized) { 1100 return; 1101 } 1102 if (!useLastSeenTarget()) { 1103 mPendingAnimationAction = REVERSE_ANIMATION; 1104 return; 1105 } 1106 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1107 Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java"); 1108 } 1109 mStarted = true; 1110 nReverse(mSetPtr, this, ++mLastListenerId); 1111 if (mListener != null) { 1112 mListener.onAnimationStart(null); 1113 } 1114 } 1115 1116 public long getAnimatorNativePtr() { 1117 return mSetPtr; 1118 } 1119 1120 boolean canReverse() { 1121 return mIsReversible; 1122 } 1123 1124 boolean isStarted() { 1125 return mStarted; 1126 } 1127 1128 boolean isRunning() { 1129 if (!mInitialized) { 1130 return false; 1131 } 1132 return mStarted; 1133 } 1134 1135 void setListener(AnimatorListener listener) { 1136 mListener = listener; 1137 } 1138 1139 void removeListener() { 1140 mListener = null; 1141 } 1142 1143 private void onAnimationEnd(int listenerId) { 1144 if (listenerId != mLastListenerId) { 1145 return; 1146 } 1147 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 1148 Log.d(LOGTAG, "on finished called from native"); 1149 } 1150 mStarted = false; 1151 if (mListener != null) { 1152 mListener.onAnimationEnd(null); 1153 } 1154 } 1155 1156 // onFinished: should be called from native 1157 private static void callOnFinished(VectorDrawableAnimator set, int id) { 1158 set.onAnimationEnd(id); 1159 } 1160 } 1161 1162 private static native long nCreateAnimatorSet(); 1163 private static native void nAddAnimator(long setPtr, long propertyValuesHolder, 1164 long nativeInterpolator, long startDelay, long duration, int repeatCount); 1165 1166 private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId, 1167 float startValue, float endValue); 1168 1169 private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr, 1170 long endValuePtr); 1171 private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId, 1172 int startValue, int endValue); 1173 private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId, 1174 float startValue, float endValue); 1175 private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, 1176 float endValue); 1177 private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length); 1178 private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set, int id); 1179 private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set, int id); 1180 private static native void nEnd(long animatorSetPtr); 1181 private static native void nReset(long animatorSetPtr); 1182} 1183