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