Animation.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
1/* 2 * Copyright (C) 2006 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.view.animation; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.util.AttributeSet; 22import android.util.TypedValue; 23 24/** 25 * Abstraction for an Animation that can be applied to Views, Surfaces, or 26 * other objects. See the {@link android.view.animation animation package 27 * description file}. 28 */ 29public abstract class Animation { 30 /** 31 * Repeat the animation indefinitely. 32 */ 33 public static final int INFINITE = -1; 34 35 /** 36 * When the animation reaches the end and the repeat count is INFINTE_REPEAT 37 * or a positive value, the animation restarts from the beginning. 38 */ 39 public static final int RESTART = 1; 40 41 /** 42 * When the animation reaches the end and the repeat count is INFINTE_REPEAT 43 * or a positive value, the animation plays backward (and then forward again). 44 */ 45 public static final int REVERSE = 2; 46 47 /** 48 * Can be used as the start time to indicate the start time should be the current 49 * time when {@link #getTransformation(long, Transformation)} is invoked for the 50 * first animation frame. This can is useful for short animations. 51 */ 52 public static final int START_ON_FIRST_FRAME = -1; 53 54 /** 55 * The specified dimension is an absolute number of pixels. 56 */ 57 public static final int ABSOLUTE = 0; 58 59 /** 60 * The specified dimension holds a float and should be multiplied by the 61 * height or width of the object being animated. 62 */ 63 public static final int RELATIVE_TO_SELF = 1; 64 65 /** 66 * The specified dimension holds a float and should be multiplied by the 67 * height or width of the parent of the object being animated. 68 */ 69 public static final int RELATIVE_TO_PARENT = 2; 70 71 /** 72 * Requests that the content being animated be kept in its current Z 73 * order. 74 */ 75 public static final int ZORDER_NORMAL = 0; 76 77 /** 78 * Requests that the content being animated be forced on top of all other 79 * content for the duration of the animation. 80 */ 81 public static final int ZORDER_TOP = 1; 82 83 /** 84 * Requests that the content being animated be forced under all other 85 * content for the duration of the animation. 86 */ 87 public static final int ZORDER_BOTTOM = -1; 88 89 /** 90 * Set by {@link #getTransformation(long, Transformation)} when the animation ends. 91 */ 92 boolean mEnded = false; 93 94 /** 95 * Set by {@link #getTransformation(long, Transformation)} when the animation starts. 96 */ 97 boolean mStarted = false; 98 99 /** 100 * Set by {@link #getTransformation(long, Transformation)} when the animation repeats 101 * in REVERSE mode. 102 */ 103 boolean mCycleFlip = false; 104 105 /** 106 * This value must be set to true by {@link #initialize(int, int, int, int)}. It 107 * indicates the animation was successfully initialized and can be played. 108 */ 109 boolean mInitialized = false; 110 111 /** 112 * Indicates whether the animation transformation should be applied before the 113 * animation starts. 114 */ 115 boolean mFillBefore = true; 116 117 /** 118 * Indicates whether the animation transformation should be applied after the 119 * animation ends. 120 */ 121 boolean mFillAfter = false; 122 123 /** 124 * The time in milliseconds at which the animation must start; 125 */ 126 long mStartTime = -1; 127 128 /** 129 * The delay in milliseconds after which the animation must start. When the 130 * start offset is > 0, the start time of the animation is startTime + startOffset. 131 */ 132 long mStartOffset; 133 134 /** 135 * The duration of one animation cycle in milliseconds. 136 */ 137 long mDuration; 138 139 /** 140 * The number of times the animation must repeat. By default, an animation repeats 141 * indefinitely. 142 */ 143 int mRepeatCount = 0; 144 145 /** 146 * Indicates how many times the animation was repeated. 147 */ 148 int mRepeated = 0; 149 150 /** 151 * The behavior of the animation when it repeats. The repeat mode is either 152 * {@link #RESTART} or {@link #REVERSE}. 153 * 154 */ 155 int mRepeatMode = RESTART; 156 157 /** 158 * The interpolator used by the animation to smooth the movement. 159 */ 160 Interpolator mInterpolator; 161 162 /** 163 * The animation listener to be notified when the animation starts, ends or repeats. 164 */ 165 AnimationListener mListener; 166 167 /** 168 * Desired Z order mode during animation. 169 */ 170 private int mZAdjustment; 171 172 // Indicates what was the last value returned by getTransformation() 173 private boolean mMore = true; 174 175 /** 176 * Creates a new animation with a duration of 0ms, the default interpolator, with 177 * fillBefore set to true and fillAfter set to false 178 */ 179 public Animation() { 180 ensureInterpolator(); 181 } 182 183 /** 184 * Creates a new animation whose parameters come from the specified context and 185 * attributes set. 186 * 187 * @param context the application environment 188 * @param attrs the set of attributes holding the animation parameters 189 */ 190 public Animation(Context context, AttributeSet attrs) { 191 TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animation); 192 193 setDuration((long) a.getInt(com.android.internal.R.styleable.Animation_duration, 0)); 194 setStartOffset((long) a.getInt(com.android.internal.R.styleable.Animation_startOffset, 0)); 195 196 setFillBefore(a.getBoolean(com.android.internal.R.styleable.Animation_fillBefore, mFillBefore)); 197 setFillAfter(a.getBoolean(com.android.internal.R.styleable.Animation_fillAfter, mFillAfter)); 198 199 final int resID = a.getResourceId(com.android.internal.R.styleable.Animation_interpolator, 0); 200 if (resID > 0) { 201 setInterpolator(context, resID); 202 } 203 204 setRepeatCount(a.getInt(com.android.internal.R.styleable.Animation_repeatCount, mRepeatCount)); 205 setRepeatMode(a.getInt(com.android.internal.R.styleable.Animation_repeatMode, RESTART)); 206 207 setZAdjustment(a.getInt(com.android.internal.R.styleable.Animation_zAdjustment, ZORDER_NORMAL)); 208 209 ensureInterpolator(); 210 211 a.recycle(); 212 } 213 214 /** 215 * Reset the initialization state of this animation. 216 * 217 * @see #initialize(int, int, int, int) 218 */ 219 public void reset() { 220 mInitialized = false; 221 mCycleFlip = false; 222 mRepeated = 0; 223 mMore = true; 224 } 225 226 /** 227 * Whether or not the animation has been initialized. 228 * 229 * @return Has this animation been initialized. 230 * @see #initialize(int, int, int, int) 231 */ 232 public boolean isInitialized() { 233 return mInitialized; 234 } 235 236 /** 237 * Initialize this animation with the dimensions of the object being 238 * animated as well as the objects parents. (This is to support animation 239 * sizes being specifed relative to these dimensions.) 240 * 241 * <p>Objects that interpret a Animations should call this method when 242 * the sizes of the object being animated and its parent are known, and 243 * before calling {@link #getTransformation}. 244 * 245 * 246 * @param width Width of the object being animated 247 * @param height Height of the object being animated 248 * @param parentWidth Width of the animated object's parent 249 * @param parentHeight Height of the animated object's parent 250 */ 251 public void initialize(int width, int height, int parentWidth, int parentHeight) { 252 mInitialized = true; 253 mCycleFlip = false; 254 mRepeated = 0; 255 mMore = true; 256 } 257 258 /** 259 * Sets the acceleration curve for this animation. The interpolator is loaded as 260 * a resource from the specified context. 261 * 262 * @param context The application environment 263 * @param resID The resource identifier of the interpolator to load 264 * @attr ref android.R.styleable#Animation_interpolator 265 */ 266 public void setInterpolator(Context context, int resID) { 267 setInterpolator(AnimationUtils.loadInterpolator(context, resID)); 268 } 269 270 /** 271 * Sets the acceleration curve for this animation. Defaults to a linear 272 * interpolation. 273 * 274 * @param i The interpolator which defines the acceleration curve 275 * @attr ref android.R.styleable#Animation_interpolator 276 */ 277 public void setInterpolator(Interpolator i) { 278 mInterpolator = i; 279 } 280 281 /** 282 * When this animation should start relative to the start time. This is most 283 * useful when composing complex animations using an {@link AnimationSet } 284 * where some of the animations components start at different times. 285 * 286 * @param startOffset When this Animation should start, in milliseconds from 287 * the start time of the root AnimationSet. 288 * @attr ref android.R.styleable#Animation_startOffset 289 */ 290 public void setStartOffset(long startOffset) { 291 mStartOffset = startOffset; 292 } 293 294 /** 295 * How long this animation should last. 296 * 297 * @param durationMillis Duration in milliseconds 298 * @attr ref android.R.styleable#Animation_duration 299 */ 300 public void setDuration(long durationMillis) { 301 mDuration = durationMillis; 302 } 303 304 /** 305 * Ensure that the duration that this animation will run is not longer 306 * than <var>durationMillis</var>. In addition to adjusting the duration 307 * itself, this ensures that the repeat count also will not make it run 308 * longer than the given time. 309 * 310 * @param durationMillis The maximum duration the animation is allowed 311 * to run. 312 */ 313 public void restrictDuration(long durationMillis) { 314 if (mStartOffset > durationMillis) { 315 mStartOffset = durationMillis; 316 mDuration = 0; 317 mRepeatCount = 0; 318 return; 319 } 320 321 long dur = mDuration + mStartOffset; 322 if (dur > durationMillis) { 323 mDuration = dur = durationMillis-mStartOffset; 324 } 325 if (mRepeatCount < 0 || mRepeatCount > durationMillis 326 || (dur*mRepeatCount) > durationMillis) { 327 mRepeatCount = (int)(durationMillis/dur); 328 } 329 } 330 331 /** 332 * How much to scale the duration by. 333 * 334 * @param scale The amount to scale the duration. 335 */ 336 public void scaleCurrentDuration(float scale) { 337 mDuration = (long) (mDuration * scale); 338 } 339 340 /** 341 * When this animation should start. When the start time is set to 342 * {@link #START_ON_FIRST_FRAME}, the animation will start the first time 343 * {@link #getTransformation(long, Transformation)} is invoked. The time passed 344 * to this method should be obtained by calling 345 * {@link AnimationUtils#currentAnimationTimeMillis()} instead of 346 * {@link System#currentTimeMillis()}. 347 * 348 * @param startTimeMillis the start time in milliseconds 349 */ 350 public void setStartTime(long startTimeMillis) { 351 mStartTime = startTimeMillis; 352 mStarted = mEnded = false; 353 mCycleFlip = false; 354 mRepeated = 0; 355 mMore = true; 356 } 357 358 /** 359 * Convenience method to start the animation the first time 360 * {@link #getTransformation(long, Transformation)} is invoked. 361 */ 362 public void start() { 363 setStartTime(-1); 364 } 365 366 /** 367 * Convenience method to start the animation at the current time in 368 * milliseconds. 369 */ 370 public void startNow() { 371 setStartTime(AnimationUtils.currentAnimationTimeMillis()); 372 } 373 374 /** 375 * Defines what this animation should do when it reaches the end. This 376 * setting is applied only when the repeat count is either greater than 377 * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}. 378 * 379 * @param repeatMode {@link #RESTART} or {@link #REVERSE} 380 * @attr ref android.R.styleable#Animation_repeatMode 381 */ 382 public void setRepeatMode(int repeatMode) { 383 mRepeatMode = repeatMode; 384 } 385 386 /** 387 * Sets how many times the animation should be repeated. If the repeat 388 * count is 0, the animation is never repeated. If the repeat count is 389 * greater than 0 or {@link #INFINITE}, the repeat mode will be taken 390 * into account. The repeat count if 0 by default. 391 * 392 * @param repeatCount the number of times the animation should be repeated 393 * @attr ref android.R.styleable#Animation_repeatCount 394 */ 395 public void setRepeatCount(int repeatCount) { 396 if (repeatCount < 0) { 397 repeatCount = INFINITE; 398 } 399 mRepeatCount = repeatCount; 400 } 401 402 /** 403 * If fillBefore is true, this animation will apply its transformation 404 * before the start time of the animation. Defaults to false if not set. 405 * Note that this applies when using an {@link 406 * android.view.animation.AnimationSet AnimationSet} to chain 407 * animations. The transformation is not applied before the AnimationSet 408 * itself starts. 409 * 410 * @param fillBefore true if the animation should apply its transformation before it starts 411 * @attr ref android.R.styleable#Animation_fillBefore 412 */ 413 public void setFillBefore(boolean fillBefore) { 414 mFillBefore = fillBefore; 415 } 416 417 /** 418 * If fillAfter is true, the transformation that this animation performed 419 * will persist when it is finished. Defaults to false if not set. 420 * Note that this applies when using an {@link 421 * android.view.animation.AnimationSet AnimationSet} to chain 422 * animations. The transformation is not applied before the AnimationSet 423 * itself starts. 424 * 425 * @param fillAfter true if the animation should apply its transformation after it ends 426 * @attr ref android.R.styleable#Animation_fillAfter 427 */ 428 public void setFillAfter(boolean fillAfter) { 429 mFillAfter = fillAfter; 430 } 431 432 /** 433 * Set the Z ordering mode to use while running the animation. 434 * 435 * @param zAdjustment The desired mode, one of {@link #ZORDER_NORMAL}, 436 * {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}. 437 * @attr ref android.R.styleable#Animation_zAdjustment 438 */ 439 public void setZAdjustment(int zAdjustment) { 440 mZAdjustment = zAdjustment; 441 } 442 443 /** 444 * Gets the acceleration curve type for this animation. 445 * 446 * @return the {@link Interpolator} associated to this animation 447 * @attr ref android.R.styleable#Animation_interpolator 448 */ 449 public Interpolator getInterpolator() { 450 return mInterpolator; 451 } 452 453 /** 454 * When this animation should start. If the animation has not startet yet, 455 * this method might return {@link #START_ON_FIRST_FRAME}. 456 * 457 * @return the time in milliseconds when the animation should start or 458 * {@link #START_ON_FIRST_FRAME} 459 */ 460 public long getStartTime() { 461 return mStartTime; 462 } 463 464 /** 465 * How long this animation should last 466 * 467 * @return the duration in milliseconds of the animation 468 * @attr ref android.R.styleable#Animation_duration 469 */ 470 public long getDuration() { 471 return mDuration; 472 } 473 474 /** 475 * When this animation should start, relative to StartTime 476 * 477 * @return the start offset in milliseconds 478 * @attr ref android.R.styleable#Animation_startOffset 479 */ 480 public long getStartOffset() { 481 return mStartOffset; 482 } 483 484 /** 485 * Defines what this animation should do when it reaches the end. 486 * 487 * @return either one of {@link #REVERSE} or {@link #RESTART} 488 * @attr ref android.R.styleable#Animation_repeatMode 489 */ 490 public int getRepeatMode() { 491 return mRepeatMode; 492 } 493 494 /** 495 * Defines how many times the animation should repeat. The default value 496 * is 0. 497 * 498 * @return the number of times the animation should repeat, or {@link #INFINITE} 499 * @attr ref android.R.styleable#Animation_repeatCount 500 */ 501 public int getRepeatCount() { 502 return mRepeatCount; 503 } 504 505 /** 506 * If fillBefore is true, this animation will apply its transformation 507 * before the start time of the animation. 508 * 509 * @return true if the animation applies its transformation before it starts 510 * @attr ref android.R.styleable#Animation_fillBefore 511 */ 512 public boolean getFillBefore() { 513 return mFillBefore; 514 } 515 516 /** 517 * If fillAfter is true, this animation will apply its transformation 518 * after the end time of the animation. 519 * 520 * @return true if the animation applies its transformation after it ends 521 * @attr ref android.R.styleable#Animation_fillAfter 522 */ 523 public boolean getFillAfter() { 524 return mFillAfter; 525 } 526 527 /** 528 * Returns the Z ordering mode to use while running the animation as 529 * previously set by {@link #setZAdjustment}. 530 * 531 * @return Returns one of {@link #ZORDER_NORMAL}, 532 * {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}. 533 * @attr ref android.R.styleable#Animation_zAdjustment 534 */ 535 public int getZAdjustment() { 536 return mZAdjustment; 537 } 538 539 /** 540 * <p>Indicates whether or not this animation will affect the transformation 541 * matrix. For instance, a fade animation will not affect the matrix whereas 542 * a scale animation will.</p> 543 * 544 * @return true if this animation will change the transformation matrix 545 */ 546 public boolean willChangeTransformationMatrix() { 547 // assume we will change the matrix 548 return true; 549 } 550 551 /** 552 * <p>Indicates whether or not this animation will affect the bounds of the 553 * animated view. For instance, a fade animation will not affect the bounds 554 * whereas a 200% scale animation will.</p> 555 * 556 * @return true if this animation will change the view's bounds 557 */ 558 public boolean willChangeBounds() { 559 // assume we will change the bounds 560 return true; 561 } 562 563 /** 564 * <p>Binds an animation listener to this animation. The animation listener 565 * is notified of animation events such as the end of the animation or the 566 * repetition of the animation.</p> 567 * 568 * @param listener the animation listener to be notified 569 */ 570 public void setAnimationListener(AnimationListener listener) { 571 mListener = listener; 572 } 573 574 /** 575 * Gurantees that this animation has an interpolator. Will use 576 * a AccelerateDecelerateInterpolator is nothing else was specified. 577 */ 578 protected void ensureInterpolator() { 579 if (mInterpolator == null) { 580 mInterpolator = new AccelerateDecelerateInterpolator(); 581 } 582 } 583 584 /** 585 * Compute a hint at how long the entire animation may last, in milliseconds. 586 * Animations can be written to cause themselves to run for a different 587 * duration than what is computed here, but generally this should be 588 * accurate. 589 */ 590 public long computeDurationHint() { 591 return (getStartOffset() + getDuration()) * (getRepeatCount() + 1); 592 } 593 594 /** 595 * Gets the transformation to apply at a specified point in time. Implementations of this 596 * method should always replace the specified Transformation or document they are doing 597 * otherwise. 598 * 599 * @param currentTime Where we are in the animation. This is wall clock time. 600 * @param outTransformation A tranformation object that is provided by the 601 * caller and will be filled in by the animation. 602 * @return True if the animation is still running 603 */ 604 public boolean getTransformation(long currentTime, Transformation outTransformation) { 605 if (mStartTime == -1) { 606 mStartTime = currentTime; 607 } 608 609 final long startOffset = getStartOffset(); 610 float normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / 611 (float) mDuration; 612 613 boolean expired = normalizedTime >= 1.0f; 614 // Pin time to 0.0 to 1.0 range 615 normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); 616 mMore = !expired; 617 618 if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { 619 if (!mStarted) { 620 if (mListener != null) { 621 mListener.onAnimationStart(this); 622 } 623 mStarted = true; 624 } 625 626 if (mCycleFlip) { 627 normalizedTime = 1.0f - normalizedTime; 628 } 629 630 final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); 631 applyTransformation(interpolatedTime, outTransformation); 632 } 633 634 if (expired) { 635 if (mRepeatCount == mRepeated) { 636 if (!mEnded) { 637 if (mListener != null) { 638 mListener.onAnimationEnd(this); 639 } 640 mEnded = true; 641 } 642 } else { 643 if (mRepeatCount > 0) { 644 mRepeated++; 645 } 646 647 if (mRepeatMode == REVERSE) { 648 mCycleFlip = !mCycleFlip; 649 } 650 651 mStartTime = -1; 652 mMore = true; 653 654 if (mListener != null) { 655 mListener.onAnimationRepeat(this); 656 } 657 } 658 } 659 660 return mMore; 661 } 662 663 /** 664 * <p>Indicates whether this animation has started or not.</p> 665 * 666 * @return true if the animation has started, false otherwise 667 */ 668 public boolean hasStarted() { 669 return mStarted; 670 } 671 672 /** 673 * <p>Indicates whether this animation has ended or not.</p> 674 * 675 * @return true if the animation has ended, false otherwise 676 */ 677 public boolean hasEnded() { 678 return mEnded; 679 } 680 681 /** 682 * Helper for getTransformation. Subclasses should implement this to apply 683 * their transforms given an interpolation value. Implementations of this 684 * method should always replace the specified Transformation or document 685 * they are doing otherwise. 686 * 687 * @param interpolatedTime The value of the normalized time (0.0 to 1.0) 688 * after it has been run through the interpolation function. 689 * @param t The Transofrmation object to fill in with the current 690 * transforms. 691 */ 692 protected void applyTransformation(float interpolatedTime, Transformation t) { 693 } 694 695 /** 696 * Convert the information in the description of a size to an actual 697 * dimension 698 * 699 * @param type One of Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or 700 * Animation.RELATIVE_TO_PARENT. 701 * @param value The dimension associated with the type parameter 702 * @param size The size of the object being animated 703 * @param parentSize The size of the parent of the object being animated 704 * @return The dimension to use for the animation 705 */ 706 protected float resolveSize(int type, float value, int size, int parentSize) { 707 switch (type) { 708 case ABSOLUTE: 709 return value; 710 case RELATIVE_TO_SELF: 711 return size * value; 712 case RELATIVE_TO_PARENT: 713 return parentSize * value; 714 default: 715 return value; 716 } 717 } 718 719 /** 720 * Utility class to parse a string description of a size. 721 */ 722 protected static class Description { 723 /** 724 * One of Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or 725 * Animation.RELATIVE_TO_PARENT. 726 */ 727 public int type; 728 729 /** 730 * The absolute or relative dimension for this Description. 731 */ 732 public float value; 733 734 /** 735 * Size descriptions can appear inthree forms: 736 * <ol> 737 * <li>An absolute size. This is represented by a number.</li> 738 * <li>A size relative to the size of the object being animated. This 739 * is represented by a number followed by "%".</li> * 740 * <li>A size relative to the size of the parent of object being 741 * animated. This is represented by a number followed by "%p".</li> 742 * </ol> 743 * @param value The typed value to parse 744 * @return The parsed version of the description 745 */ 746 static Description parseValue(TypedValue value) { 747 Description d = new Description(); 748 if (value == null) { 749 d.type = ABSOLUTE; 750 d.value = 0; 751 } else { 752 if (value.type == TypedValue.TYPE_FRACTION) { 753 d.type = (value.data & TypedValue.COMPLEX_UNIT_MASK) == 754 TypedValue.COMPLEX_UNIT_FRACTION_PARENT ? 755 RELATIVE_TO_PARENT : RELATIVE_TO_SELF; 756 d.value = TypedValue.complexToFloat(value.data); 757 return d; 758 } else if (value.type == TypedValue.TYPE_FLOAT) { 759 d.type = ABSOLUTE; 760 d.value = value.getFloat(); 761 return d; 762 } else if (value.type >= TypedValue.TYPE_FIRST_INT && 763 value.type <= TypedValue.TYPE_LAST_INT) { 764 d.type = ABSOLUTE; 765 d.value = value.data; 766 return d; 767 } 768 } 769 770 d.type = ABSOLUTE; 771 d.value = 0.0f; 772 773 return d; 774 } 775 } 776 777 /** 778 * <p>An animation listener receives notifications from an animation. 779 * Notifications indicate animation related events, such as the end or the 780 * repetition of the animation.</p> 781 */ 782 public static interface AnimationListener { 783 /** 784 * <p>Notifies the start of the animation.</p> 785 * 786 * @param animation The started animation. 787 */ 788 void onAnimationStart(Animation animation); 789 790 /** 791 * <p>Notifies the end of the animation. This callback is invoked 792 * only for animation with repeat mode set to NO_REPEAT.</p> 793 * 794 * @param animation The animation which reached its end. 795 */ 796 void onAnimationEnd(Animation animation); 797 798 /** 799 * <p>Notifies the repetition of the animation. This callback is invoked 800 * only for animation with repeat mode set to RESTART or REVERSE.</p> 801 * 802 * @param animation The animation which was repeated. 803 */ 804 void onAnimationRepeat(Animation animation); 805 } 806} 807