ValueAnimator.java revision 3dbaae1ef4f221b3626810f4ba19eec068dd6304
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.animation; 18 19import android.annotation.CallSuper; 20import android.os.Looper; 21import android.os.Trace; 22import android.util.AndroidRuntimeException; 23import android.util.Log; 24import android.view.animation.AccelerateDecelerateInterpolator; 25import android.view.animation.AnimationUtils; 26import android.view.animation.LinearInterpolator; 27 28import java.util.ArrayList; 29import java.util.HashMap; 30 31/** 32 * This class provides a simple timing engine for running animations 33 * which calculate animated values and set them on target objects. 34 * 35 * <p>There is a single timing pulse that all animations use. It runs in a 36 * custom handler to ensure that property changes happen on the UI thread.</p> 37 * 38 * <p>By default, ValueAnimator uses non-linear time interpolation, via the 39 * {@link AccelerateDecelerateInterpolator} class, which accelerates into and decelerates 40 * out of an animation. This behavior can be changed by calling 41 * {@link ValueAnimator#setInterpolator(TimeInterpolator)}.</p> 42 * 43 * <p>Animators can be created from either code or resource files. Here is an example 44 * of a ValueAnimator resource file:</p> 45 * 46 * {@sample development/samples/ApiDemos/res/anim/animator.xml ValueAnimatorResources} 47 * 48 * <p>It is also possible to use a combination of {@link PropertyValuesHolder} and 49 * {@link Keyframe} resource tags to create a multi-step animation. 50 * Note that you can specify explicit fractional values (from 0 to 1) for 51 * each keyframe to determine when, in the overall duration, the animation should arrive at that 52 * value. Alternatively, you can leave the fractions off and the keyframes will be equally 53 * distributed within the total duration:</p> 54 * 55 * {@sample development/samples/ApiDemos/res/anim/value_animator_pvh_kf.xml 56 * ValueAnimatorKeyframeResources} 57 * 58 * <div class="special reference"> 59 * <h3>Developer Guides</h3> 60 * <p>For more information about animating with {@code ValueAnimator}, read the 61 * <a href="{@docRoot}guide/topics/graphics/prop-animation.html#value-animator">Property 62 * Animation</a> developer guide.</p> 63 * </div> 64 */ 65@SuppressWarnings("unchecked") 66public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback { 67 private static final String TAG = "ValueAnimator"; 68 private static final boolean DEBUG = false; 69 70 /** 71 * Internal constants 72 */ 73 private static float sDurationScale = 1.0f; 74 75 /** 76 * Internal variables 77 * NOTE: This object implements the clone() method, making a deep copy of any referenced 78 * objects. As other non-trivial fields are added to this class, make sure to add logic 79 * to clone() to make deep copies of them. 80 */ 81 82 /** 83 * The first time that the animation's animateFrame() method is called. This time is used to 84 * determine elapsed time (and therefore the elapsed fraction) in subsequent calls 85 * to animateFrame(). 86 * 87 * Whenever mStartTime is set, you must also update mStartTimeCommitted. 88 */ 89 long mStartTime; 90 91 /** 92 * When true, the start time has been firmly committed as a chosen reference point in 93 * time by which the progress of the animation will be evaluated. When false, the 94 * start time may be updated when the first animation frame is committed so as 95 * to compensate for jank that may have occurred between when the start time was 96 * initialized and when the frame was actually drawn. 97 * 98 * This flag is generally set to false during the first frame of the animation 99 * when the animation playing state transitions from STOPPED to RUNNING or 100 * resumes after having been paused. This flag is set to true when the start time 101 * is firmly committed and should not be further compensated for jank. 102 */ 103 boolean mStartTimeCommitted; 104 105 /** 106 * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked 107 * to a value. 108 */ 109 float mSeekFraction = -1; 110 111 /** 112 * Set on the next frame after pause() is called, used to calculate a new startTime 113 * or delayStartTime which allows the animator to continue from the point at which 114 * it was paused. If negative, has not yet been set. 115 */ 116 private long mPauseTime; 117 118 /** 119 * Set when an animator is resumed. This triggers logic in the next frame which 120 * actually resumes the animator. 121 */ 122 private boolean mResumed = false; 123 124 // The time interpolator to be used if none is set on the animation 125 private static final TimeInterpolator sDefaultInterpolator = 126 new AccelerateDecelerateInterpolator(); 127 128 /** 129 * Used to indicate whether the animation is currently playing in reverse. This causes the 130 * elapsed fraction to be inverted to calculate the appropriate values. 131 */ 132 private boolean mPlayingBackwards = false; 133 134 /** 135 * Flag to indicate whether this animator is playing in reverse mode, specifically 136 * by being started or interrupted by a call to reverse(). This flag is different than 137 * mPlayingBackwards, which indicates merely whether the current iteration of the 138 * animator is playing in reverse. It is used in corner cases to determine proper end 139 * behavior. 140 */ 141 private boolean mReversing; 142 143 /** 144 * This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the 145 * repeatCount (if repeatCount!=INFINITE), the animation ends 146 */ 147 private int mCurrentIteration = 0; 148 149 /** 150 * Tracks current elapsed/eased fraction, for querying in getAnimatedFraction(). 151 */ 152 private float mCurrentFraction = 0f; 153 154 /** 155 * Tracks the time (in milliseconds) when the last frame arrived. 156 */ 157 private long mLastFrameTime = 0; 158 159 /** 160 * Additional playing state to indicate whether an animator has been start()'d. There is 161 * some lag between a call to start() and the first animation frame. We should still note 162 * that the animation has been started, even if it's first animation frame has not yet 163 * happened, and reflect that state in isRunning(). 164 * Note that delayed animations are different: they are not started until their first 165 * animation frame, which occurs after their delay elapses. 166 */ 167 private boolean mRunning = false; 168 169 /** 170 * Additional playing state to indicate whether an animator has been start()'d, whether or 171 * not there is a nonzero startDelay. 172 */ 173 private boolean mStarted = false; 174 175 /** 176 * Tracks whether we've notified listeners of the onAnimationStart() event. This can be 177 * complex to keep track of since we notify listeners at different times depending on 178 * startDelay and whether start() was called before end(). 179 */ 180 private boolean mStartListenersCalled = false; 181 182 /** 183 * Flag that denotes whether the animation is set up and ready to go. Used to 184 * set up animation that has not yet been started. 185 */ 186 boolean mInitialized = false; 187 188 /** 189 * Flag that tracks whether animation has been requested to end. 190 */ 191 private boolean mAnimationEndRequested = false; 192 193 // 194 // Backing variables 195 // 196 197 // How long the animation should last in ms 198 private long mUnscaledDuration = 300; 199 private long mDuration = (long)(mUnscaledDuration * sDurationScale); 200 201 // The amount of time in ms to delay starting the animation after start() is called 202 long mStartDelay = 0; 203 private long mUnscaledStartDelay = 0; 204 205 // The number of times the animation will repeat. The default is 0, which means the animation 206 // will play only once 207 private int mRepeatCount = 0; 208 209 /** 210 * The type of repetition that will occur when repeatMode is nonzero. RESTART means the 211 * animation will start from the beginning on every new cycle. REVERSE means the animation 212 * will reverse directions on each iteration. 213 */ 214 private int mRepeatMode = RESTART; 215 216 /** 217 * The time interpolator to be used. The elapsed fraction of the animation will be passed 218 * through this interpolator to calculate the interpolated fraction, which is then used to 219 * calculate the animated values. 220 */ 221 private TimeInterpolator mInterpolator = sDefaultInterpolator; 222 223 /** 224 * The set of listeners to be sent events through the life of an animation. 225 */ 226 ArrayList<AnimatorUpdateListener> mUpdateListeners = null; 227 228 /** 229 * The property/value sets being animated. 230 */ 231 PropertyValuesHolder[] mValues; 232 233 /** 234 * A hashmap of the PropertyValuesHolder objects. This map is used to lookup animated values 235 * by property name during calls to getAnimatedValue(String). 236 */ 237 HashMap<String, PropertyValuesHolder> mValuesMap; 238 239 /** 240 * Public constants 241 */ 242 243 /** 244 * When the animation reaches the end and <code>repeatCount</code> is INFINITE 245 * or a positive value, the animation restarts from the beginning. 246 */ 247 public static final int RESTART = 1; 248 /** 249 * When the animation reaches the end and <code>repeatCount</code> is INFINITE 250 * or a positive value, the animation reverses direction on every iteration. 251 */ 252 public static final int REVERSE = 2; 253 /** 254 * This value used used with the {@link #setRepeatCount(int)} property to repeat 255 * the animation indefinitely. 256 */ 257 public static final int INFINITE = -1; 258 259 /** 260 * @hide 261 */ 262 public static void setDurationScale(float durationScale) { 263 sDurationScale = durationScale; 264 } 265 266 /** 267 * @hide 268 */ 269 public static float getDurationScale() { 270 return sDurationScale; 271 } 272 273 /** 274 * Creates a new ValueAnimator object. This default constructor is primarily for 275 * use internally; the factory methods which take parameters are more generally 276 * useful. 277 */ 278 public ValueAnimator() { 279 } 280 281 /** 282 * Constructs and returns a ValueAnimator that animates between int values. A single 283 * value implies that that value is the one being animated to. However, this is not typically 284 * useful in a ValueAnimator object because there is no way for the object to determine the 285 * starting value for the animation (unlike ObjectAnimator, which can derive that value 286 * from the target object and property being animated). Therefore, there should typically 287 * be two or more values. 288 * 289 * @param values A set of values that the animation will animate between over time. 290 * @return A ValueAnimator object that is set up to animate between the given values. 291 */ 292 public static ValueAnimator ofInt(int... values) { 293 ValueAnimator anim = new ValueAnimator(); 294 anim.setIntValues(values); 295 return anim; 296 } 297 298 /** 299 * Constructs and returns a ValueAnimator that animates between color values. A single 300 * value implies that that value is the one being animated to. However, this is not typically 301 * useful in a ValueAnimator object because there is no way for the object to determine the 302 * starting value for the animation (unlike ObjectAnimator, which can derive that value 303 * from the target object and property being animated). Therefore, there should typically 304 * be two or more values. 305 * 306 * @param values A set of values that the animation will animate between over time. 307 * @return A ValueAnimator object that is set up to animate between the given values. 308 */ 309 public static ValueAnimator ofArgb(int... values) { 310 ValueAnimator anim = new ValueAnimator(); 311 anim.setIntValues(values); 312 anim.setEvaluator(ArgbEvaluator.getInstance()); 313 return anim; 314 } 315 316 /** 317 * Constructs and returns a ValueAnimator that animates between float values. A single 318 * value implies that that value is the one being animated to. However, this is not typically 319 * useful in a ValueAnimator object because there is no way for the object to determine the 320 * starting value for the animation (unlike ObjectAnimator, which can derive that value 321 * from the target object and property being animated). Therefore, there should typically 322 * be two or more values. 323 * 324 * @param values A set of values that the animation will animate between over time. 325 * @return A ValueAnimator object that is set up to animate between the given values. 326 */ 327 public static ValueAnimator ofFloat(float... values) { 328 ValueAnimator anim = new ValueAnimator(); 329 anim.setFloatValues(values); 330 return anim; 331 } 332 333 /** 334 * Constructs and returns a ValueAnimator that animates between the values 335 * specified in the PropertyValuesHolder objects. 336 * 337 * @param values A set of PropertyValuesHolder objects whose values will be animated 338 * between over time. 339 * @return A ValueAnimator object that is set up to animate between the given values. 340 */ 341 public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) { 342 ValueAnimator anim = new ValueAnimator(); 343 anim.setValues(values); 344 return anim; 345 } 346 /** 347 * Constructs and returns a ValueAnimator that animates between Object values. A single 348 * value implies that that value is the one being animated to. However, this is not typically 349 * useful in a ValueAnimator object because there is no way for the object to determine the 350 * starting value for the animation (unlike ObjectAnimator, which can derive that value 351 * from the target object and property being animated). Therefore, there should typically 352 * be two or more values. 353 * 354 * <p>Since ValueAnimator does not know how to animate between arbitrary Objects, this 355 * factory method also takes a TypeEvaluator object that the ValueAnimator will use 356 * to perform that interpolation. 357 * 358 * @param evaluator A TypeEvaluator that will be called on each animation frame to 359 * provide the ncessry interpolation between the Object values to derive the animated 360 * value. 361 * @param values A set of values that the animation will animate between over time. 362 * @return A ValueAnimator object that is set up to animate between the given values. 363 */ 364 public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) { 365 ValueAnimator anim = new ValueAnimator(); 366 anim.setObjectValues(values); 367 anim.setEvaluator(evaluator); 368 return anim; 369 } 370 371 /** 372 * Sets int values that will be animated between. A single 373 * value implies that that value is the one being animated to. However, this is not typically 374 * useful in a ValueAnimator object because there is no way for the object to determine the 375 * starting value for the animation (unlike ObjectAnimator, which can derive that value 376 * from the target object and property being animated). Therefore, there should typically 377 * be two or more values. 378 * 379 * <p>If there are already multiple sets of values defined for this ValueAnimator via more 380 * than one PropertyValuesHolder object, this method will set the values for the first 381 * of those objects.</p> 382 * 383 * @param values A set of values that the animation will animate between over time. 384 */ 385 public void setIntValues(int... values) { 386 if (values == null || values.length == 0) { 387 return; 388 } 389 if (mValues == null || mValues.length == 0) { 390 setValues(PropertyValuesHolder.ofInt("", values)); 391 } else { 392 PropertyValuesHolder valuesHolder = mValues[0]; 393 valuesHolder.setIntValues(values); 394 } 395 // New property/values/target should cause re-initialization prior to starting 396 mInitialized = false; 397 } 398 399 /** 400 * Sets float values that will be animated between. A single 401 * value implies that that value is the one being animated to. However, this is not typically 402 * useful in a ValueAnimator object because there is no way for the object to determine the 403 * starting value for the animation (unlike ObjectAnimator, which can derive that value 404 * from the target object and property being animated). Therefore, there should typically 405 * be two or more values. 406 * 407 * <p>If there are already multiple sets of values defined for this ValueAnimator via more 408 * than one PropertyValuesHolder object, this method will set the values for the first 409 * of those objects.</p> 410 * 411 * @param values A set of values that the animation will animate between over time. 412 */ 413 public void setFloatValues(float... values) { 414 if (values == null || values.length == 0) { 415 return; 416 } 417 if (mValues == null || mValues.length == 0) { 418 setValues(PropertyValuesHolder.ofFloat("", values)); 419 } else { 420 PropertyValuesHolder valuesHolder = mValues[0]; 421 valuesHolder.setFloatValues(values); 422 } 423 // New property/values/target should cause re-initialization prior to starting 424 mInitialized = false; 425 } 426 427 /** 428 * Sets the values to animate between for this animation. A single 429 * value implies that that value is the one being animated to. However, this is not typically 430 * useful in a ValueAnimator object because there is no way for the object to determine the 431 * starting value for the animation (unlike ObjectAnimator, which can derive that value 432 * from the target object and property being animated). Therefore, there should typically 433 * be two or more values. 434 * 435 * <p>If there are already multiple sets of values defined for this ValueAnimator via more 436 * than one PropertyValuesHolder object, this method will set the values for the first 437 * of those objects.</p> 438 * 439 * <p>There should be a TypeEvaluator set on the ValueAnimator that knows how to interpolate 440 * between these value objects. ValueAnimator only knows how to interpolate between the 441 * primitive types specified in the other setValues() methods.</p> 442 * 443 * @param values The set of values to animate between. 444 */ 445 public void setObjectValues(Object... values) { 446 if (values == null || values.length == 0) { 447 return; 448 } 449 if (mValues == null || mValues.length == 0) { 450 setValues(PropertyValuesHolder.ofObject("", null, values)); 451 } else { 452 PropertyValuesHolder valuesHolder = mValues[0]; 453 valuesHolder.setObjectValues(values); 454 } 455 // New property/values/target should cause re-initialization prior to starting 456 mInitialized = false; 457 } 458 459 /** 460 * Sets the values, per property, being animated between. This function is called internally 461 * by the constructors of ValueAnimator that take a list of values. But a ValueAnimator can 462 * be constructed without values and this method can be called to set the values manually 463 * instead. 464 * 465 * @param values The set of values, per property, being animated between. 466 */ 467 public void setValues(PropertyValuesHolder... values) { 468 int numValues = values.length; 469 mValues = values; 470 mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues); 471 for (int i = 0; i < numValues; ++i) { 472 PropertyValuesHolder valuesHolder = values[i]; 473 mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); 474 } 475 // New property/values/target should cause re-initialization prior to starting 476 mInitialized = false; 477 } 478 479 /** 480 * Returns the values that this ValueAnimator animates between. These values are stored in 481 * PropertyValuesHolder objects, even if the ValueAnimator was created with a simple list 482 * of value objects instead. 483 * 484 * @return PropertyValuesHolder[] An array of PropertyValuesHolder objects which hold the 485 * values, per property, that define the animation. 486 */ 487 public PropertyValuesHolder[] getValues() { 488 return mValues; 489 } 490 491 /** 492 * This function is called immediately before processing the first animation 493 * frame of an animation. If there is a nonzero <code>startDelay</code>, the 494 * function is called after that delay ends. 495 * It takes care of the final initialization steps for the 496 * animation. 497 * 498 * <p>Overrides of this method should call the superclass method to ensure 499 * that internal mechanisms for the animation are set up correctly.</p> 500 */ 501 @CallSuper 502 void initAnimation() { 503 if (!mInitialized) { 504 int numValues = mValues.length; 505 for (int i = 0; i < numValues; ++i) { 506 mValues[i].init(); 507 } 508 mInitialized = true; 509 } 510 } 511 512 /** 513 * Sets the length of the animation. The default duration is 300 milliseconds. 514 * 515 * @param duration The length of the animation, in milliseconds. This value cannot 516 * be negative. 517 * @return ValueAnimator The object called with setDuration(). This return 518 * value makes it easier to compose statements together that construct and then set the 519 * duration, as in <code>ValueAnimator.ofInt(0, 10).setDuration(500).start()</code>. 520 */ 521 @Override 522 public ValueAnimator setDuration(long duration) { 523 if (duration < 0) { 524 throw new IllegalArgumentException("Animators cannot have negative duration: " + 525 duration); 526 } 527 mUnscaledDuration = duration; 528 updateScaledDuration(); 529 return this; 530 } 531 532 private void updateScaledDuration() { 533 mDuration = (long)(mUnscaledDuration * sDurationScale); 534 } 535 536 /** 537 * Gets the length of the animation. The default duration is 300 milliseconds. 538 * 539 * @return The length of the animation, in milliseconds. 540 */ 541 @Override 542 public long getDuration() { 543 return mUnscaledDuration; 544 } 545 546 /** 547 * @hide 548 */ 549 @Override 550 public long getTotalDuration() { 551 if (mRepeatCount == INFINITE) { 552 return DURATION_INFINITE; 553 } else { 554 return mUnscaledStartDelay + (mUnscaledDuration * (mRepeatCount + 1)); 555 } 556 } 557 558 /** 559 * Sets the position of the animation to the specified point in time. This time should 560 * be between 0 and the total duration of the animation, including any repetition. If 561 * the animation has not yet been started, then it will not advance forward after it is 562 * set to this time; it will simply set the time to this value and perform any appropriate 563 * actions based on that time. If the animation is already running, then setCurrentPlayTime() 564 * will set the current playing time to this value and continue playing from that point. 565 * 566 * @param playTime The time, in milliseconds, to which the animation is advanced or rewound. 567 */ 568 public void setCurrentPlayTime(long playTime) { 569 float fraction = mUnscaledDuration > 0 ? (float) playTime / mUnscaledDuration : 1; 570 setCurrentFraction(fraction); 571 } 572 573 /** 574 * Sets the position of the animation to the specified fraction. This fraction should 575 * be between 0 and the total fraction of the animation, including any repetition. That is, 576 * a fraction of 0 will position the animation at the beginning, a value of 1 at the end, 577 * and a value of 2 at the end of a reversing animator that repeats once. If 578 * the animation has not yet been started, then it will not advance forward after it is 579 * set to this fraction; it will simply set the fraction to this value and perform any 580 * appropriate actions based on that fraction. If the animation is already running, then 581 * setCurrentFraction() will set the current fraction to this value and continue 582 * playing from that point. {@link Animator.AnimatorListener} events are not called 583 * due to changing the fraction; those events are only processed while the animation 584 * is running. 585 * 586 * @param fraction The fraction to which the animation is advanced or rewound. Values 587 * outside the range of 0 to the maximum fraction for the animator will be clamped to 588 * the correct range. 589 */ 590 public void setCurrentFraction(float fraction) { 591 initAnimation(); 592 if (fraction < 0) { 593 fraction = 0; 594 } 595 int iteration = (int) fraction; 596 if (fraction == 1) { 597 iteration -= 1; 598 } else if (fraction > 1) { 599 if (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE) { 600 if (mRepeatMode == REVERSE) { 601 mPlayingBackwards = (iteration % 2) != 0; 602 } 603 fraction = fraction % 1f; 604 } else { 605 fraction = 1; 606 iteration -= 1; 607 } 608 } else { 609 mPlayingBackwards = mReversing; 610 } 611 mCurrentIteration = iteration; 612 long seekTime = (long) (mDuration * fraction); 613 long currentTime = AnimationUtils.currentAnimationTimeMillis(); 614 mStartTime = currentTime - seekTime; 615 mStartTimeCommitted = true; // do not allow start time to be compensated for jank 616 if (!mRunning) { 617 mSeekFraction = fraction; 618 } 619 if (mPlayingBackwards) { 620 fraction = 1f - fraction; 621 } 622 animateValue(fraction); 623 } 624 625 /** 626 * Gets the current position of the animation in time, which is equal to the current 627 * time minus the time that the animation started. An animation that is not yet started will 628 * return a value of zero. 629 * 630 * @return The current position in time of the animation. 631 */ 632 public long getCurrentPlayTime() { 633 if (!mInitialized || !mStarted) { 634 return 0; 635 } 636 return AnimationUtils.currentAnimationTimeMillis() - mStartTime; 637 } 638 639 /** 640 * The amount of time, in milliseconds, to delay starting the animation after 641 * {@link #start()} is called. 642 * 643 * @return the number of milliseconds to delay running the animation 644 */ 645 @Override 646 public long getStartDelay() { 647 return mUnscaledStartDelay; 648 } 649 650 /** 651 * The amount of time, in milliseconds, to delay starting the animation after 652 * {@link #start()} is called. 653 654 * @param startDelay The amount of the delay, in milliseconds 655 */ 656 @Override 657 public void setStartDelay(long startDelay) { 658 this.mStartDelay = (long)(startDelay * sDurationScale); 659 mUnscaledStartDelay = startDelay; 660 } 661 662 /** 663 * The amount of time, in milliseconds, between each frame of the animation. This is a 664 * requested time that the animation will attempt to honor, but the actual delay between 665 * frames may be different, depending on system load and capabilities. This is a static 666 * function because the same delay will be applied to all animations, since they are all 667 * run off of a single timing loop. 668 * 669 * The frame delay may be ignored when the animation system uses an external timing 670 * source, such as the display refresh rate (vsync), to govern animations. 671 * 672 * @return the requested time between frames, in milliseconds 673 */ 674 public static long getFrameDelay() { 675 return AnimationHandler.getInstance().getFrameDelay(); 676 } 677 678 /** 679 * The amount of time, in milliseconds, between each frame of the animation. This is a 680 * requested time that the animation will attempt to honor, but the actual delay between 681 * frames may be different, depending on system load and capabilities. This is a static 682 * function because the same delay will be applied to all animations, since they are all 683 * run off of a single timing loop. 684 * 685 * The frame delay may be ignored when the animation system uses an external timing 686 * source, such as the display refresh rate (vsync), to govern animations. 687 * 688 * @param frameDelay the requested time between frames, in milliseconds 689 */ 690 public static void setFrameDelay(long frameDelay) { 691 AnimationHandler.getInstance().setFrameDelay(frameDelay); 692 } 693 694 /** 695 * The most recent value calculated by this <code>ValueAnimator</code> when there is just one 696 * property being animated. This value is only sensible while the animation is running. The main 697 * purpose for this read-only property is to retrieve the value from the <code>ValueAnimator</code> 698 * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which 699 * is called during each animation frame, immediately after the value is calculated. 700 * 701 * @return animatedValue The value most recently calculated by this <code>ValueAnimator</code> for 702 * the single property being animated. If there are several properties being animated 703 * (specified by several PropertyValuesHolder objects in the constructor), this function 704 * returns the animated value for the first of those objects. 705 */ 706 public Object getAnimatedValue() { 707 if (mValues != null && mValues.length > 0) { 708 return mValues[0].getAnimatedValue(); 709 } 710 // Shouldn't get here; should always have values unless ValueAnimator was set up wrong 711 return null; 712 } 713 714 /** 715 * The most recent value calculated by this <code>ValueAnimator</code> for <code>propertyName</code>. 716 * The main purpose for this read-only property is to retrieve the value from the 717 * <code>ValueAnimator</code> during a call to 718 * {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which 719 * is called during each animation frame, immediately after the value is calculated. 720 * 721 * @return animatedValue The value most recently calculated for the named property 722 * by this <code>ValueAnimator</code>. 723 */ 724 public Object getAnimatedValue(String propertyName) { 725 PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName); 726 if (valuesHolder != null) { 727 return valuesHolder.getAnimatedValue(); 728 } else { 729 // At least avoid crashing if called with bogus propertyName 730 return null; 731 } 732 } 733 734 /** 735 * Sets how many times the animation should be repeated. If the repeat 736 * count is 0, the animation is never repeated. If the repeat count is 737 * greater than 0 or {@link #INFINITE}, the repeat mode will be taken 738 * into account. The repeat count is 0 by default. 739 * 740 * @param value the number of times the animation should be repeated 741 */ 742 public void setRepeatCount(int value) { 743 mRepeatCount = value; 744 } 745 /** 746 * Defines how many times the animation should repeat. The default value 747 * is 0. 748 * 749 * @return the number of times the animation should repeat, or {@link #INFINITE} 750 */ 751 public int getRepeatCount() { 752 return mRepeatCount; 753 } 754 755 /** 756 * Defines what this animation should do when it reaches the end. This 757 * setting is applied only when the repeat count is either greater than 758 * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}. 759 * 760 * @param value {@link #RESTART} or {@link #REVERSE} 761 */ 762 public void setRepeatMode(int value) { 763 mRepeatMode = value; 764 } 765 766 /** 767 * Defines what this animation should do when it reaches the end. 768 * 769 * @return either one of {@link #REVERSE} or {@link #RESTART} 770 */ 771 public int getRepeatMode() { 772 return mRepeatMode; 773 } 774 775 /** 776 * Adds a listener to the set of listeners that are sent update events through the life of 777 * an animation. This method is called on all listeners for every frame of the animation, 778 * after the values for the animation have been calculated. 779 * 780 * @param listener the listener to be added to the current set of listeners for this animation. 781 */ 782 public void addUpdateListener(AnimatorUpdateListener listener) { 783 if (mUpdateListeners == null) { 784 mUpdateListeners = new ArrayList<AnimatorUpdateListener>(); 785 } 786 mUpdateListeners.add(listener); 787 } 788 789 /** 790 * Removes all listeners from the set listening to frame updates for this animation. 791 */ 792 public void removeAllUpdateListeners() { 793 if (mUpdateListeners == null) { 794 return; 795 } 796 mUpdateListeners.clear(); 797 mUpdateListeners = null; 798 } 799 800 /** 801 * Removes a listener from the set listening to frame updates for this animation. 802 * 803 * @param listener the listener to be removed from the current set of update listeners 804 * for this animation. 805 */ 806 public void removeUpdateListener(AnimatorUpdateListener listener) { 807 if (mUpdateListeners == null) { 808 return; 809 } 810 mUpdateListeners.remove(listener); 811 if (mUpdateListeners.size() == 0) { 812 mUpdateListeners = null; 813 } 814 } 815 816 817 /** 818 * The time interpolator used in calculating the elapsed fraction of this animation. The 819 * interpolator determines whether the animation runs with linear or non-linear motion, 820 * such as acceleration and deceleration. The default value is 821 * {@link android.view.animation.AccelerateDecelerateInterpolator} 822 * 823 * @param value the interpolator to be used by this animation. A value of <code>null</code> 824 * will result in linear interpolation. 825 */ 826 @Override 827 public void setInterpolator(TimeInterpolator value) { 828 if (value != null) { 829 mInterpolator = value; 830 } else { 831 mInterpolator = new LinearInterpolator(); 832 } 833 } 834 835 /** 836 * Returns the timing interpolator that this ValueAnimator uses. 837 * 838 * @return The timing interpolator for this ValueAnimator. 839 */ 840 @Override 841 public TimeInterpolator getInterpolator() { 842 return mInterpolator; 843 } 844 845 /** 846 * The type evaluator to be used when calculating the animated values of this animation. 847 * The system will automatically assign a float or int evaluator based on the type 848 * of <code>startValue</code> and <code>endValue</code> in the constructor. But if these values 849 * are not one of these primitive types, or if different evaluation is desired (such as is 850 * necessary with int values that represent colors), a custom evaluator needs to be assigned. 851 * For example, when running an animation on color values, the {@link ArgbEvaluator} 852 * should be used to get correct RGB color interpolation. 853 * 854 * <p>If this ValueAnimator has only one set of values being animated between, this evaluator 855 * will be used for that set. If there are several sets of values being animated, which is 856 * the case if PropertyValuesHolder objects were set on the ValueAnimator, then the evaluator 857 * is assigned just to the first PropertyValuesHolder object.</p> 858 * 859 * @param value the evaluator to be used this animation 860 */ 861 public void setEvaluator(TypeEvaluator value) { 862 if (value != null && mValues != null && mValues.length > 0) { 863 mValues[0].setEvaluator(value); 864 } 865 } 866 867 private void notifyStartListeners() { 868 if (mListeners != null && !mStartListenersCalled) { 869 ArrayList<AnimatorListener> tmpListeners = 870 (ArrayList<AnimatorListener>) mListeners.clone(); 871 int numListeners = tmpListeners.size(); 872 for (int i = 0; i < numListeners; ++i) { 873 tmpListeners.get(i).onAnimationStart(this); 874 } 875 } 876 mStartListenersCalled = true; 877 } 878 879 /** 880 * Start the animation playing. This version of start() takes a boolean flag that indicates 881 * whether the animation should play in reverse. The flag is usually false, but may be set 882 * to true if called from the reverse() method. 883 * 884 * <p>The animation started by calling this method will be run on the thread that called 885 * this method. This thread should have a Looper on it (a runtime exception will be thrown if 886 * this is not the case). Also, if the animation will animate 887 * properties of objects in the view hierarchy, then the calling thread should be the UI 888 * thread for that view hierarchy.</p> 889 * 890 * @param playBackwards Whether the ValueAnimator should start playing in reverse. 891 */ 892 private void start(boolean playBackwards) { 893 if (Looper.myLooper() == null) { 894 throw new AndroidRuntimeException("Animators may only be run on Looper threads"); 895 } 896 mReversing = playBackwards; 897 mPlayingBackwards = playBackwards; 898 if (playBackwards && mSeekFraction != -1) { 899 if (mSeekFraction == 0 && mCurrentIteration == 0) { 900 // special case: reversing from seek-to-0 should act as if not seeked at all 901 mSeekFraction = 0; 902 } else if (mRepeatCount == INFINITE) { 903 mSeekFraction = 1 - (mSeekFraction % 1); 904 } else { 905 mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction); 906 } 907 mCurrentIteration = (int) mSeekFraction; 908 mSeekFraction = mSeekFraction % 1; 909 } 910 if (mCurrentIteration > 0 && mRepeatMode == REVERSE && 911 (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) { 912 // if we were seeked to some other iteration in a reversing animator, 913 // figure out the correct direction to start playing based on the iteration 914 if (playBackwards) { 915 mPlayingBackwards = (mCurrentIteration % 2) == 0; 916 } else { 917 mPlayingBackwards = (mCurrentIteration % 2) != 0; 918 } 919 } 920 mStarted = true; 921 mPaused = false; 922 mRunning = false; 923 mAnimationEndRequested = false; 924 updateScaledDuration(); // in case the scale factor has changed since creation time 925 AnimationHandler animationHandler = AnimationHandler.getInstance(); 926 animationHandler.addAnimationFrameCallback(this, mStartDelay); 927 } 928 929 @Override 930 public void start() { 931 start(false); 932 } 933 934 @Override 935 public void cancel() { 936 if (Looper.myLooper() == null) { 937 throw new AndroidRuntimeException("Animators may only be run on Looper threads"); 938 } 939 940 // If end has already been requested, through a previous end() or cancel() call, no-op 941 // until animation starts again. 942 if (mAnimationEndRequested) { 943 return; 944 } 945 946 // Only cancel if the animation is actually running or has been started and is about 947 // to run 948 // Only notify listeners if the animator has actually started 949 if ((mStarted || mRunning) && mListeners != null) { 950 if (!mRunning) { 951 // If it's not yet running, then start listeners weren't called. Call them now. 952 notifyStartListeners(); 953 } 954 ArrayList<AnimatorListener> tmpListeners = 955 (ArrayList<AnimatorListener>) mListeners.clone(); 956 for (AnimatorListener listener : tmpListeners) { 957 listener.onAnimationCancel(this); 958 } 959 } 960 endAnimation(); 961 962 } 963 964 @Override 965 public void end() { 966 if (Looper.myLooper() == null) { 967 throw new AndroidRuntimeException("Animators may only be run on Looper threads"); 968 } 969 if (!mRunning) { 970 // Special case if the animation has not yet started; get it ready for ending 971 startAnimation(); 972 mStarted = true; 973 } else if (!mInitialized) { 974 initAnimation(); 975 } 976 animateValue(mPlayingBackwards ? 0f : 1f); 977 endAnimation(); 978 } 979 980 @Override 981 public void resume() { 982 if (mPaused) { 983 mResumed = true; 984 } 985 super.resume(); 986 } 987 988 @Override 989 public void pause() { 990 boolean previouslyPaused = mPaused; 991 super.pause(); 992 if (!previouslyPaused && mPaused) { 993 mPauseTime = -1; 994 mResumed = false; 995 } 996 } 997 998 @Override 999 public boolean isRunning() { 1000 return mRunning; 1001 } 1002 1003 @Override 1004 public boolean isStarted() { 1005 return mStarted; 1006 } 1007 1008 /** 1009 * Plays the ValueAnimator in reverse. If the animation is already running, 1010 * it will stop itself and play backwards from the point reached when reverse was called. 1011 * If the animation is not currently running, then it will start from the end and 1012 * play backwards. This behavior is only set for the current animation; future playing 1013 * of the animation will use the default behavior of playing forward. 1014 */ 1015 @Override 1016 public void reverse() { 1017 mPlayingBackwards = !mPlayingBackwards; 1018 if (mRunning) { 1019 long currentTime = AnimationUtils.currentAnimationTimeMillis(); 1020 long currentPlayTime = currentTime - mStartTime; 1021 long timeLeft = mDuration - currentPlayTime; 1022 mStartTime = currentTime - timeLeft; 1023 mStartTimeCommitted = true; // do not allow start time to be compensated for jank 1024 mReversing = !mReversing; 1025 } else if (mStarted) { 1026 end(); 1027 } else { 1028 start(true); 1029 } 1030 } 1031 1032 /** 1033 * @hide 1034 */ 1035 @Override 1036 public boolean canReverse() { 1037 return true; 1038 } 1039 1040 /** 1041 * Called internally to end an animation by removing it from the animations list. Must be 1042 * called on the UI thread. 1043 */ 1044 private void endAnimation() { 1045 if (mAnimationEndRequested) { 1046 return; 1047 } 1048 AnimationHandler handler = AnimationHandler.getInstance(); 1049 handler.removeCallback(this); 1050 1051 mAnimationEndRequested = true; 1052 mPaused = false; 1053 if ((mStarted || mRunning) && mListeners != null) { 1054 if (!mRunning) { 1055 // If it's not yet running, then start listeners weren't called. Call them now. 1056 notifyStartListeners(); 1057 } 1058 ArrayList<AnimatorListener> tmpListeners = 1059 (ArrayList<AnimatorListener>) mListeners.clone(); 1060 int numListeners = tmpListeners.size(); 1061 for (int i = 0; i < numListeners; ++i) { 1062 tmpListeners.get(i).onAnimationEnd(this); 1063 } 1064 } 1065 mRunning = false; 1066 mStarted = false; 1067 mStartListenersCalled = false; 1068 mPlayingBackwards = false; 1069 mReversing = false; 1070 mCurrentIteration = 0; 1071 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 1072 Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(), 1073 System.identityHashCode(this)); 1074 } 1075 } 1076 1077 /** 1078 * Called internally to start an animation by adding it to the active animations list. Must be 1079 * called on the UI thread. 1080 */ 1081 private void startAnimation() { 1082 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 1083 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(), 1084 System.identityHashCode(this)); 1085 } 1086 initAnimation(); 1087 mRunning = true; 1088 if (mListeners != null) { 1089 notifyStartListeners(); 1090 } 1091 } 1092 1093 /** 1094 * Returns the name of this animator for debugging purposes. 1095 */ 1096 String getNameForTrace() { 1097 return "animator"; 1098 } 1099 1100 /** 1101 * Applies an adjustment to the animation to compensate for jank between when 1102 * the animation first ran and when the frame was drawn. 1103 * @hide 1104 */ 1105 public void commitAnimationFrame(long frameTime) { 1106 if (!mStartTimeCommitted) { 1107 mStartTimeCommitted = true; 1108 long adjustment = frameTime - mLastFrameTime; 1109 if (adjustment > 0) { 1110 mStartTime += adjustment; 1111 if (DEBUG) { 1112 Log.d(TAG, "Adjusted start time by " + adjustment + " ms: " + toString()); 1113 } 1114 } 1115 } 1116 } 1117 1118 /** 1119 * This internal function processes a single animation frame for a given animation. The 1120 * currentTime parameter is the timing pulse sent by the handler, used to calculate the 1121 * elapsed duration, and therefore 1122 * the elapsed fraction, of the animation. The return value indicates whether the animation 1123 * should be ended (which happens when the elapsed time of the animation exceeds the 1124 * animation's duration, including the repeatCount). 1125 * 1126 * @param currentTime The current time, as tracked by the static timing handler 1127 * @return true if the animation's duration, including any repetitions due to 1128 * <code>repeatCount</code> has been exceeded and the animation should be ended. 1129 */ 1130 boolean animateBasedOnTime(long currentTime) { 1131 boolean done = false; 1132 if (mRunning) { 1133 float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f; 1134 if (mDuration == 0 && mRepeatCount != INFINITE) { 1135 // Skip to the end 1136 mCurrentIteration = mRepeatCount; 1137 if (!mReversing) { 1138 mPlayingBackwards = false; 1139 } 1140 } 1141 if (fraction >= 1f) { 1142 if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) { 1143 // Time to repeat 1144 if (mListeners != null) { 1145 int numListeners = mListeners.size(); 1146 for (int i = 0; i < numListeners; ++i) { 1147 mListeners.get(i).onAnimationRepeat(this); 1148 } 1149 } 1150 if (mRepeatMode == REVERSE) { 1151 mPlayingBackwards = !mPlayingBackwards; 1152 } 1153 mCurrentIteration += (int) fraction; 1154 fraction = fraction % 1f; 1155 mStartTime += mDuration; 1156 // Note: We do not need to update the value of mStartTimeCommitted here 1157 // since we just added a duration offset. 1158 } else { 1159 done = true; 1160 fraction = Math.min(fraction, 1.0f); 1161 } 1162 } 1163 if (mPlayingBackwards) { 1164 fraction = 1f - fraction; 1165 } 1166 animateValue(fraction); 1167 } 1168 return done; 1169 } 1170 1171 /** 1172 * Processes a frame of the animation, adjusting the start time if needed. 1173 * 1174 * @param frameTime The frame time. 1175 * @return true if the animation has ended. 1176 * @hide 1177 */ 1178 public final void doAnimationFrame(long frameTime) { 1179 mLastFrameTime = frameTime; 1180 AnimationHandler handler = AnimationHandler.getInstance(); 1181 if (!mRunning) { 1182 // First frame 1183 handler.addOneShotCommitCallback(this); 1184 startAnimation(); 1185 if (mSeekFraction < 0) { 1186 mStartTime = frameTime; 1187 } else { 1188 long seekTime = (long) (mDuration * mSeekFraction); 1189 mStartTime = frameTime - seekTime; 1190 mSeekFraction = -1; 1191 } 1192 mStartTimeCommitted = false; // allow start time to be compensated for jank 1193 } 1194 if (mPaused) { 1195 if (mPauseTime < 0) { 1196 mPauseTime = frameTime; 1197 } 1198 return; 1199 } else if (mResumed) { 1200 mResumed = false; 1201 if (mPauseTime > 0) { 1202 // Offset by the duration that the animation was paused 1203 mStartTime += (frameTime - mPauseTime); 1204 mStartTimeCommitted = false; // allow start time to be compensated for jank 1205 } 1206 handler.addOneShotCommitCallback(this); 1207 } 1208 // The frame time might be before the start time during the first frame of 1209 // an animation. The "current time" must always be on or after the start 1210 // time to avoid animating frames at negative time intervals. In practice, this 1211 // is very rare and only happens when seeking backwards. 1212 final long currentTime = Math.max(frameTime, mStartTime); 1213 boolean finished = animateBasedOnTime(currentTime); 1214 1215 if (finished) { 1216 endAnimation(); 1217 } 1218 } 1219 1220 /** 1221 * Returns the current animation fraction, which is the elapsed/interpolated fraction used in 1222 * the most recent frame update on the animation. 1223 * 1224 * @return Elapsed/interpolated fraction of the animation. 1225 */ 1226 public float getAnimatedFraction() { 1227 return mCurrentFraction; 1228 } 1229 1230 /** 1231 * This method is called with the elapsed fraction of the animation during every 1232 * animation frame. This function turns the elapsed fraction into an interpolated fraction 1233 * and then into an animated value (from the evaluator. The function is called mostly during 1234 * animation updates, but it is also called when the <code>end()</code> 1235 * function is called, to set the final value on the property. 1236 * 1237 * <p>Overrides of this method must call the superclass to perform the calculation 1238 * of the animated value.</p> 1239 * 1240 * @param fraction The elapsed fraction of the animation. 1241 */ 1242 @CallSuper 1243 void animateValue(float fraction) { 1244 fraction = mInterpolator.getInterpolation(fraction); 1245 mCurrentFraction = fraction; 1246 int numValues = mValues.length; 1247 for (int i = 0; i < numValues; ++i) { 1248 mValues[i].calculateValue(fraction); 1249 } 1250 if (mUpdateListeners != null) { 1251 int numListeners = mUpdateListeners.size(); 1252 for (int i = 0; i < numListeners; ++i) { 1253 mUpdateListeners.get(i).onAnimationUpdate(this); 1254 } 1255 } 1256 } 1257 1258 @Override 1259 public ValueAnimator clone() { 1260 final ValueAnimator anim = (ValueAnimator) super.clone(); 1261 if (mUpdateListeners != null) { 1262 anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>(mUpdateListeners); 1263 } 1264 anim.mSeekFraction = -1; 1265 anim.mPlayingBackwards = false; 1266 anim.mReversing = false; 1267 anim.mCurrentIteration = 0; 1268 anim.mInitialized = false; 1269 anim.mStarted = false; 1270 anim.mRunning = false; 1271 anim.mPaused = false; 1272 anim.mResumed = false; 1273 anim.mStartListenersCalled = false; 1274 anim.mStartTime = 0; 1275 anim.mStartTimeCommitted = false; 1276 anim.mAnimationEndRequested = false; 1277 anim.mPauseTime = 0; 1278 anim.mLastFrameTime = 0; 1279 anim.mCurrentFraction = 0; 1280 1281 PropertyValuesHolder[] oldValues = mValues; 1282 if (oldValues != null) { 1283 int numValues = oldValues.length; 1284 anim.mValues = new PropertyValuesHolder[numValues]; 1285 anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues); 1286 for (int i = 0; i < numValues; ++i) { 1287 PropertyValuesHolder newValuesHolder = oldValues[i].clone(); 1288 anim.mValues[i] = newValuesHolder; 1289 anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder); 1290 } 1291 } 1292 return anim; 1293 } 1294 1295 /** 1296 * Implementors of this interface can add themselves as update listeners 1297 * to an <code>ValueAnimator</code> instance to receive callbacks on every animation 1298 * frame, after the current frame's values have been calculated for that 1299 * <code>ValueAnimator</code>. 1300 */ 1301 public static interface AnimatorUpdateListener { 1302 /** 1303 * <p>Notifies the occurrence of another frame of the animation.</p> 1304 * 1305 * @param animation The animation which was repeated. 1306 */ 1307 void onAnimationUpdate(ValueAnimator animation); 1308 1309 } 1310 1311 /** 1312 * Return the number of animations currently running. 1313 * 1314 * Used by StrictMode internally to annotate violations. 1315 * May be called on arbitrary threads! 1316 * 1317 * @hide 1318 */ 1319 public static int getCurrentAnimationsCount() { 1320 return AnimationHandler.getAnimationCount(); 1321 } 1322 1323 @Override 1324 public String toString() { 1325 String returnVal = "ValueAnimator@" + Integer.toHexString(hashCode()); 1326 if (mValues != null) { 1327 for (int i = 0; i < mValues.length; ++i) { 1328 returnVal += "\n " + mValues[i].toString(); 1329 } 1330 } 1331 return returnVal; 1332 } 1333 1334 /** 1335 * <p>Whether or not the ValueAnimator is allowed to run asynchronously off of 1336 * the UI thread. This is a hint that informs the ValueAnimator that it is 1337 * OK to run the animation off-thread, however ValueAnimator may decide 1338 * that it must run the animation on the UI thread anyway. For example if there 1339 * is an {@link AnimatorUpdateListener} the animation will run on the UI thread, 1340 * regardless of the value of this hint.</p> 1341 * 1342 * <p>Regardless of whether or not the animation runs asynchronously, all 1343 * listener callbacks will be called on the UI thread.</p> 1344 * 1345 * <p>To be able to use this hint the following must be true:</p> 1346 * <ol> 1347 * <li>{@link #getAnimatedFraction()} is not needed (it will return undefined values).</li> 1348 * <li>The animator is immutable while {@link #isStarted()} is true. Requests 1349 * to change values, duration, delay, etc... may be ignored.</li> 1350 * <li>Lifecycle callback events may be asynchronous. Events such as 1351 * {@link Animator.AnimatorListener#onAnimationEnd(Animator)} or 1352 * {@link Animator.AnimatorListener#onAnimationRepeat(Animator)} may end up delayed 1353 * as they must be posted back to the UI thread, and any actions performed 1354 * by those callbacks (such as starting new animations) will not happen 1355 * in the same frame.</li> 1356 * <li>State change requests ({@link #cancel()}, {@link #end()}, {@link #reverse()}, etc...) 1357 * may be asynchronous. It is guaranteed that all state changes that are 1358 * performed on the UI thread in the same frame will be applied as a single 1359 * atomic update, however that frame may be the current frame, 1360 * the next frame, or some future frame. This will also impact the observed 1361 * state of the Animator. For example, {@link #isStarted()} may still return true 1362 * after a call to {@link #end()}. Using the lifecycle callbacks is preferred over 1363 * queries to {@link #isStarted()}, {@link #isRunning()}, and {@link #isPaused()} 1364 * for this reason.</li> 1365 * </ol> 1366 * @hide 1367 */ 1368 @Override 1369 public void setAllowRunningAsynchronously(boolean mayRunAsync) { 1370 // It is up to subclasses to support this, if they can. 1371 } 1372} 1373