1 /* 2 * Copyright (C) 2017 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 17 package androidx.dynamicanimation.animation; 18 19 import android.os.Looper; 20 import android.util.AndroidRuntimeException; 21 import android.view.View; 22 23 import androidx.annotation.FloatRange; 24 import androidx.annotation.RestrictTo; 25 import androidx.core.view.ViewCompat; 26 27 import java.util.ArrayList; 28 29 /** 30 * This class is the base class of physics-based animations. It manages the animation's 31 * lifecycle such as {@link #start()} and {@link #cancel()}. This base class also handles the common 32 * setup for all the subclass animations. For example, DynamicAnimation supports adding 33 * {@link OnAnimationEndListener} and {@link OnAnimationUpdateListener} so that the important 34 * animation events can be observed through the callbacks. The start conditions for any subclass of 35 * DynamicAnimation can be set using {@link #setStartValue(float)} and 36 * {@link #setStartVelocity(float)}. 37 * 38 * @param <T> subclass of DynamicAnimation 39 */ 40 public abstract class DynamicAnimation<T extends DynamicAnimation<T>> 41 implements AnimationHandler.AnimationFrameCallback { 42 43 /** 44 * ViewProperty holds the access of a property of a {@link View}. When an animation is 45 * created with a {@link ViewProperty} instance, the corresponding property value of the view 46 * will be updated through this ViewProperty instance. 47 */ 48 public abstract static class ViewProperty extends FloatPropertyCompat<View> { ViewProperty(String name)49 private ViewProperty(String name) { 50 super(name); 51 } 52 } 53 54 /** 55 * View's translationX property. 56 */ 57 public static final ViewProperty TRANSLATION_X = new ViewProperty("translationX") { 58 @Override 59 public void setValue(View view, float value) { 60 view.setTranslationX(value); 61 } 62 63 @Override 64 public float getValue(View view) { 65 return view.getTranslationX(); 66 } 67 }; 68 69 /** 70 * View's translationY property. 71 */ 72 public static final ViewProperty TRANSLATION_Y = new ViewProperty("translationY") { 73 @Override 74 public void setValue(View view, float value) { 75 view.setTranslationY(value); 76 } 77 78 @Override 79 public float getValue(View view) { 80 return view.getTranslationY(); 81 } 82 }; 83 84 /** 85 * View's translationZ property. 86 */ 87 public static final ViewProperty TRANSLATION_Z = new ViewProperty("translationZ") { 88 @Override 89 public void setValue(View view, float value) { 90 ViewCompat.setTranslationZ(view, value); 91 } 92 93 @Override 94 public float getValue(View view) { 95 return ViewCompat.getTranslationZ(view); 96 } 97 }; 98 99 /** 100 * View's scaleX property. 101 */ 102 public static final ViewProperty SCALE_X = new ViewProperty("scaleX") { 103 @Override 104 public void setValue(View view, float value) { 105 view.setScaleX(value); 106 } 107 108 @Override 109 public float getValue(View view) { 110 return view.getScaleX(); 111 } 112 }; 113 114 /** 115 * View's scaleY property. 116 */ 117 public static final ViewProperty SCALE_Y = new ViewProperty("scaleY") { 118 @Override 119 public void setValue(View view, float value) { 120 view.setScaleY(value); 121 } 122 123 @Override 124 public float getValue(View view) { 125 return view.getScaleY(); 126 } 127 }; 128 129 /** 130 * View's rotation property. 131 */ 132 public static final ViewProperty ROTATION = new ViewProperty("rotation") { 133 @Override 134 public void setValue(View view, float value) { 135 view.setRotation(value); 136 } 137 138 @Override 139 public float getValue(View view) { 140 return view.getRotation(); 141 } 142 }; 143 144 /** 145 * View's rotationX property. 146 */ 147 public static final ViewProperty ROTATION_X = new ViewProperty("rotationX") { 148 @Override 149 public void setValue(View view, float value) { 150 view.setRotationX(value); 151 } 152 153 @Override 154 public float getValue(View view) { 155 return view.getRotationX(); 156 } 157 }; 158 159 /** 160 * View's rotationY property. 161 */ 162 public static final ViewProperty ROTATION_Y = new ViewProperty("rotationY") { 163 @Override 164 public void setValue(View view, float value) { 165 view.setRotationY(value); 166 } 167 168 @Override 169 public float getValue(View view) { 170 return view.getRotationY(); 171 } 172 }; 173 174 /** 175 * View's x property. 176 */ 177 public static final ViewProperty X = new ViewProperty("x") { 178 @Override 179 public void setValue(View view, float value) { 180 view.setX(value); 181 } 182 183 @Override 184 public float getValue(View view) { 185 return view.getX(); 186 } 187 }; 188 189 /** 190 * View's y property. 191 */ 192 public static final ViewProperty Y = new ViewProperty("y") { 193 @Override 194 public void setValue(View view, float value) { 195 view.setY(value); 196 } 197 198 @Override 199 public float getValue(View view) { 200 return view.getY(); 201 } 202 }; 203 204 /** 205 * View's z property. 206 */ 207 public static final ViewProperty Z = new ViewProperty("z") { 208 @Override 209 public void setValue(View view, float value) { 210 ViewCompat.setZ(view, value); 211 } 212 213 @Override 214 public float getValue(View view) { 215 return ViewCompat.getZ(view); 216 } 217 }; 218 219 /** 220 * View's alpha property. 221 */ 222 public static final ViewProperty ALPHA = new ViewProperty("alpha") { 223 @Override 224 public void setValue(View view, float value) { 225 view.setAlpha(value); 226 } 227 228 @Override 229 public float getValue(View view) { 230 return view.getAlpha(); 231 } 232 }; 233 234 // Properties below are not RenderThread compatible 235 /** 236 * View's scrollX property. 237 */ 238 public static final ViewProperty SCROLL_X = new ViewProperty("scrollX") { 239 @Override 240 public void setValue(View view, float value) { 241 view.setScrollX((int) value); 242 } 243 244 @Override 245 public float getValue(View view) { 246 return view.getScrollX(); 247 } 248 }; 249 250 /** 251 * View's scrollY property. 252 */ 253 public static final ViewProperty SCROLL_Y = new ViewProperty("scrollY") { 254 @Override 255 public void setValue(View view, float value) { 256 view.setScrollY((int) value); 257 } 258 259 @Override 260 public float getValue(View view) { 261 return view.getScrollY(); 262 } 263 }; 264 265 /** 266 * The minimum visible change in pixels that can be visible to users. 267 */ 268 public static final float MIN_VISIBLE_CHANGE_PIXELS = 1f; 269 /** 270 * The minimum visible change in degrees that can be visible to users. 271 */ 272 public static final float MIN_VISIBLE_CHANGE_ROTATION_DEGREES = 1f / 10f; 273 /** 274 * The minimum visible change in alpha that can be visible to users. 275 */ 276 public static final float MIN_VISIBLE_CHANGE_ALPHA = 1f / 256f; 277 /** 278 * The minimum visible change in scale that can be visible to users. 279 */ 280 public static final float MIN_VISIBLE_CHANGE_SCALE = 1f / 500f; 281 282 // Use the max value of float to indicate an unset state. 283 private static final float UNSET = Float.MAX_VALUE; 284 285 // Multiplier to the min visible change value for value threshold 286 private static final float THRESHOLD_MULTIPLIER = 0.75f; 287 288 // Internal tracking for velocity. 289 float mVelocity = 0; 290 291 // Internal tracking for value. 292 float mValue = UNSET; 293 294 // Tracks whether start value is set. If not, the animation will obtain the value at the time 295 // of starting through the getter and use that as the starting value of the animation. 296 boolean mStartValueIsSet = false; 297 298 // Target to be animated. 299 final Object mTarget; 300 301 // View property id. 302 final FloatPropertyCompat mProperty; 303 304 // Package private tracking of animation lifecycle state. Visible to subclass animations. 305 boolean mRunning = false; 306 307 // Min and max values that defines the range of the animation values. 308 float mMaxValue = Float.MAX_VALUE; 309 float mMinValue = -mMaxValue; 310 311 // Last frame time. Always gets reset to -1 at the end of the animation. 312 private long mLastFrameTime = 0; 313 314 private float mMinVisibleChange; 315 316 // List of end listeners 317 private final ArrayList<OnAnimationEndListener> mEndListeners = new ArrayList<>(); 318 319 // List of update listeners 320 private final ArrayList<OnAnimationUpdateListener> mUpdateListeners = new ArrayList<>(); 321 322 // Internal state for value/velocity pair. 323 static class MassState { 324 float mValue; 325 float mVelocity; 326 } 327 328 /** 329 * Creates a dynamic animation with the given FloatValueHolder instance. 330 * 331 * @param floatValueHolder the FloatValueHolder instance to be animated. 332 */ DynamicAnimation(final FloatValueHolder floatValueHolder)333 DynamicAnimation(final FloatValueHolder floatValueHolder) { 334 mTarget = null; 335 mProperty = new FloatPropertyCompat("FloatValueHolder") { 336 @Override 337 public float getValue(Object object) { 338 return floatValueHolder.getValue(); 339 } 340 341 @Override 342 public void setValue(Object object, float value) { 343 floatValueHolder.setValue(value); 344 } 345 }; 346 mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS; 347 } 348 349 /** 350 * Creates a dynamic animation to animate the given property for the given {@link View} 351 * 352 * @param object the Object whose property is to be animated 353 * @param property the property to be animated 354 */ 355 DynamicAnimation(K object, FloatPropertyCompat<K> property)356 <K> DynamicAnimation(K object, FloatPropertyCompat<K> property) { 357 mTarget = object; 358 mProperty = property; 359 if (mProperty == ROTATION || mProperty == ROTATION_X 360 || mProperty == ROTATION_Y) { 361 mMinVisibleChange = MIN_VISIBLE_CHANGE_ROTATION_DEGREES; 362 } else if (mProperty == ALPHA) { 363 mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA; 364 } else if (mProperty == SCALE_X || mProperty == SCALE_Y) { 365 mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA; 366 } else { 367 mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS; 368 } 369 } 370 371 /** 372 * Sets the start value of the animation. If start value is not set, the animation will get 373 * the current value for the view's property, and use that as the start value. 374 * 375 * @param startValue start value for the animation 376 * @return the Animation whose start value is being set 377 */ setStartValue(float startValue)378 public T setStartValue(float startValue) { 379 mValue = startValue; 380 mStartValueIsSet = true; 381 return (T) this; 382 } 383 384 /** 385 * Start velocity of the animation. Default velocity is 0. Unit: change in property per 386 * second (e.g. pixels per second, scale/alpha value change per second). 387 * 388 * <p>Note when using a fixed value as the start velocity (as opposed to getting the velocity 389 * through touch events), it is recommended to define such a value in dp/second and convert it 390 * to pixel/second based on the density of the screen to achieve a consistent look across 391 * different screens. 392 * 393 * <p>To convert from dp/second to pixel/second: 394 * <pre class="prettyprint"> 395 * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, 396 * getResources().getDisplayMetrics()); 397 * </pre> 398 * 399 * @param startVelocity start velocity of the animation 400 * @return the Animation whose start velocity is being set 401 */ setStartVelocity(float startVelocity)402 public T setStartVelocity(float startVelocity) { 403 mVelocity = startVelocity; 404 return (T) this; 405 } 406 407 /** 408 * Sets the max value of the animation. Animations will not animate beyond their max value. 409 * Whether or not animation will come to an end when max value is reached is dependent on the 410 * child animation's implementation. 411 * 412 * @param max maximum value of the property to be animated 413 * @return the Animation whose max value is being set 414 */ setMaxValue(float max)415 public T setMaxValue(float max) { 416 // This max value should be checked and handled in the subclass animations, instead of 417 // assuming the end of the animations when the max/min value is hit in the base class. 418 // The reason is that hitting max/min value may just be a transient state, such as during 419 // the spring oscillation. 420 mMaxValue = max; 421 return (T) this; 422 } 423 424 /** 425 * Sets the min value of the animation. Animations will not animate beyond their min value. 426 * Whether or not animation will come to an end when min value is reached is dependent on the 427 * child animation's implementation. 428 * 429 * @param min minimum value of the property to be animated 430 * @return the Animation whose min value is being set 431 */ setMinValue(float min)432 public T setMinValue(float min) { 433 mMinValue = min; 434 return (T) this; 435 } 436 437 /** 438 * Adds an end listener to the animation for receiving onAnimationEnd callbacks. If the listener 439 * is {@code null} or has already been added to the list of listeners for the animation, no op. 440 * 441 * @param listener the listener to be added 442 * @return the animation to which the listener is added 443 */ addEndListener(OnAnimationEndListener listener)444 public T addEndListener(OnAnimationEndListener listener) { 445 if (!mEndListeners.contains(listener)) { 446 mEndListeners.add(listener); 447 } 448 return (T) this; 449 } 450 451 /** 452 * Removes the end listener from the animation, so as to stop receiving animation end callbacks. 453 * 454 * @param listener the listener to be removed 455 */ removeEndListener(OnAnimationEndListener listener)456 public void removeEndListener(OnAnimationEndListener listener) { 457 removeEntry(mEndListeners, listener); 458 } 459 460 /** 461 * Adds an update listener to the animation for receiving per-frame animation update callbacks. 462 * If the listener is {@code null} or has already been added to the list of listeners for the 463 * animation, no op. 464 * 465 * <p>Note that update listener should only be added before the start of the animation. 466 * 467 * @param listener the listener to be added 468 * @return the animation to which the listener is added 469 * @throws UnsupportedOperationException if the update listener is added after the animation has 470 * started 471 */ addUpdateListener(OnAnimationUpdateListener listener)472 public T addUpdateListener(OnAnimationUpdateListener listener) { 473 if (isRunning()) { 474 // Require update listener to be added before the animation, such as when we start 475 // the animation, we know whether the animation is RenderThread compatible. 476 throw new UnsupportedOperationException("Error: Update listeners must be added before" 477 + "the animation."); 478 } 479 if (!mUpdateListeners.contains(listener)) { 480 mUpdateListeners.add(listener); 481 } 482 return (T) this; 483 } 484 485 /** 486 * Removes the update listener from the animation, so as to stop receiving animation update 487 * callbacks. 488 * 489 * @param listener the listener to be removed 490 */ removeUpdateListener(OnAnimationUpdateListener listener)491 public void removeUpdateListener(OnAnimationUpdateListener listener) { 492 removeEntry(mUpdateListeners, listener); 493 } 494 495 496 /** 497 * This method sets the minimal change of animation value that is visible to users, which helps 498 * determine a reasonable threshold for the animation's termination condition. It is critical 499 * to set the minimal visible change for custom properties (i.e. non-<code>ViewProperty</code>s) 500 * unless the custom property is in pixels. 501 * 502 * <p>For custom properties, this minimum visible change defaults to change in pixel 503 * (i.e. {@link #MIN_VISIBLE_CHANGE_PIXELS}. It is recommended to adjust this value that is 504 * reasonable for the property to be animated. A general rule of thumb to calculate such a value 505 * is: minimum visible change = range of custom property value / corresponding pixel range. For 506 * example, if the property to be animated is a progress (from 0 to 100) that corresponds to a 507 * 200-pixel change. Then the min visible change should be 100 / 200. (i.e. 0.5). 508 * 509 * <p>It's not necessary to call this method when animating {@link ViewProperty}s, as the 510 * minimum visible change will be derived from the property. For example, if the property to be 511 * animated is in pixels (i.e. {@link #TRANSLATION_X}, {@link #TRANSLATION_Y}, 512 * {@link #TRANSLATION_Z}, @{@link #SCROLL_X} or {@link #SCROLL_Y}), the default minimum visible 513 * change is 1 (pixel). For {@link #ROTATION}, {@link #ROTATION_X} or {@link #ROTATION_Y}, the 514 * animation will use {@link #MIN_VISIBLE_CHANGE_ROTATION_DEGREES} as the min visible change, 515 * which is 1/10. Similarly, the minimum visible change for alpha ( 516 * i.e. {@link #MIN_VISIBLE_CHANGE_ALPHA} is defined as 1 / 256. 517 * 518 * @param minimumVisibleChange minimum change in property value that is visible to users 519 * @return the animation whose min visible change is being set 520 * @throws IllegalArgumentException if the given threshold is not positive 521 */ setMinimumVisibleChange(@loatRangefrom = 0.0, fromInclusive = false) float minimumVisibleChange)522 public T setMinimumVisibleChange(@FloatRange(from = 0.0, fromInclusive = false) 523 float minimumVisibleChange) { 524 if (minimumVisibleChange <= 0) { 525 throw new IllegalArgumentException("Minimum visible change must be positive."); 526 } 527 mMinVisibleChange = minimumVisibleChange; 528 setValueThreshold(minimumVisibleChange * THRESHOLD_MULTIPLIER); 529 return (T) this; 530 } 531 532 /** 533 * Returns the minimum change in the animation property that could be visibly different to 534 * users. 535 * 536 * @return minimum change in property value that is visible to users 537 */ getMinimumVisibleChange()538 public float getMinimumVisibleChange() { 539 return mMinVisibleChange; 540 } 541 542 /** 543 * Remove {@code null} entries from the list. 544 */ removeNullEntries(ArrayList<T> list)545 private static <T> void removeNullEntries(ArrayList<T> list) { 546 // Clean up null entries 547 for (int i = list.size() - 1; i >= 0; i--) { 548 if (list.get(i) == null) { 549 list.remove(i); 550 } 551 } 552 } 553 554 /** 555 * Remove an entry from the list by marking it {@code null} and clean up later. 556 */ removeEntry(ArrayList<T> list, T entry)557 private static <T> void removeEntry(ArrayList<T> list, T entry) { 558 int id = list.indexOf(entry); 559 if (id >= 0) { 560 list.set(id, null); 561 } 562 } 563 564 /****************Animation Lifecycle Management***************/ 565 566 /** 567 * Starts an animation. If the animation has already been started, no op. Note that calling 568 * {@link #start()} will not immediately set the property value to start value of the animation. 569 * The property values will be changed at each animation pulse, which happens before the draw 570 * pass. As a result, the changes will be reflected in the next frame, the same as if the values 571 * were set immediately. This method should only be called on main thread. 572 * 573 * @throws AndroidRuntimeException if this method is not called on the main thread 574 */ start()575 public void start() { 576 if (Looper.myLooper() != Looper.getMainLooper()) { 577 throw new AndroidRuntimeException("Animations may only be started on the main thread"); 578 } 579 if (!mRunning) { 580 startAnimationInternal(); 581 } 582 } 583 584 /** 585 * Cancels the on-going animation. If the animation hasn't started, no op. Note that this method 586 * should only be called on main thread. 587 * 588 * @throws AndroidRuntimeException if this method is not called on the main thread 589 */ cancel()590 public void cancel() { 591 if (Looper.myLooper() != Looper.getMainLooper()) { 592 throw new AndroidRuntimeException("Animations may only be canceled on the main thread"); 593 } 594 if (mRunning) { 595 endAnimationInternal(true); 596 } 597 } 598 599 /** 600 * Returns whether the animation is currently running. 601 * 602 * @return {@code true} if the animation is currently running, {@code false} otherwise 603 */ isRunning()604 public boolean isRunning() { 605 return mRunning; 606 } 607 608 /************************** Private APIs below ********************************/ 609 610 // This gets called when the animation is started, to finish the setup of the animation 611 // before the animation pulsing starts. startAnimationInternal()612 private void startAnimationInternal() { 613 if (!mRunning) { 614 mRunning = true; 615 if (!mStartValueIsSet) { 616 mValue = getPropertyValue(); 617 } 618 // Sanity check: 619 if (mValue > mMaxValue || mValue < mMinValue) { 620 throw new IllegalArgumentException("Starting value need to be in between min" 621 + " value and max value"); 622 } 623 AnimationHandler.getInstance().addAnimationFrameCallback(this, 0); 624 } 625 } 626 627 /** 628 * This gets call on each frame of the animation. Animation value and velocity are updated 629 * in this method based on the new frame time. The property value of the view being animated 630 * is then updated. The animation's ending conditions are also checked in this method. Once 631 * the animation reaches equilibrium, the animation will come to its end, and end listeners 632 * will be notified, if any. 633 * 634 * @hide 635 */ 636 @RestrictTo(RestrictTo.Scope.LIBRARY) 637 @Override doAnimationFrame(long frameTime)638 public boolean doAnimationFrame(long frameTime) { 639 if (mLastFrameTime == 0) { 640 // First frame. 641 mLastFrameTime = frameTime; 642 setPropertyValue(mValue); 643 return false; 644 } 645 long deltaT = frameTime - mLastFrameTime; 646 mLastFrameTime = frameTime; 647 boolean finished = updateValueAndVelocity(deltaT); 648 // Clamp value & velocity. 649 mValue = Math.min(mValue, mMaxValue); 650 mValue = Math.max(mValue, mMinValue); 651 652 setPropertyValue(mValue); 653 654 if (finished) { 655 endAnimationInternal(false); 656 } 657 return finished; 658 } 659 660 /** 661 * Updates the animation state (i.e. value and velocity). This method is package private, so 662 * subclasses can override this method to calculate the new value and velocity in their custom 663 * way. 664 * 665 * @param deltaT time elapsed in millisecond since last frame 666 * @return whether the animation has finished 667 */ updateValueAndVelocity(long deltaT)668 abstract boolean updateValueAndVelocity(long deltaT); 669 670 /** 671 * Internal method to reset the animation states when animation is finished/canceled. 672 */ endAnimationInternal(boolean canceled)673 private void endAnimationInternal(boolean canceled) { 674 mRunning = false; 675 AnimationHandler.getInstance().removeCallback(this); 676 mLastFrameTime = 0; 677 mStartValueIsSet = false; 678 for (int i = 0; i < mEndListeners.size(); i++) { 679 if (mEndListeners.get(i) != null) { 680 mEndListeners.get(i).onAnimationEnd(this, canceled, mValue, mVelocity); 681 } 682 } 683 removeNullEntries(mEndListeners); 684 } 685 686 /** 687 * Updates the property value through the corresponding setter. 688 */ setPropertyValue(float value)689 void setPropertyValue(float value) { 690 mProperty.setValue(mTarget, value); 691 for (int i = 0; i < mUpdateListeners.size(); i++) { 692 if (mUpdateListeners.get(i) != null) { 693 mUpdateListeners.get(i).onAnimationUpdate(this, mValue, mVelocity); 694 } 695 } 696 removeNullEntries(mUpdateListeners); 697 } 698 699 /** 700 * Returns the default threshold. 701 */ getValueThreshold()702 float getValueThreshold() { 703 return mMinVisibleChange * THRESHOLD_MULTIPLIER; 704 } 705 706 /** 707 * Obtain the property value through the corresponding getter. 708 */ getPropertyValue()709 private float getPropertyValue() { 710 return mProperty.getValue(mTarget); 711 } 712 713 /****************Sub class animations**************/ 714 /** 715 * Returns the acceleration at the given value with the given velocity. 716 **/ getAcceleration(float value, float velocity)717 abstract float getAcceleration(float value, float velocity); 718 719 /** 720 * Returns whether the animation has reached equilibrium. 721 */ isAtEquilibrium(float value, float velocity)722 abstract boolean isAtEquilibrium(float value, float velocity); 723 724 /** 725 * Updates the default value threshold for the animation based on the property to be animated. 726 */ setValueThreshold(float threshold)727 abstract void setValueThreshold(float threshold); 728 729 /** 730 * An animation listener that receives end notifications from an animation. 731 */ 732 public interface OnAnimationEndListener { 733 /** 734 * Notifies the end of an animation. Note that this callback will be invoked not only when 735 * an animation reach equilibrium, but also when the animation is canceled. 736 * 737 * @param animation animation that has ended or was canceled 738 * @param canceled whether the animation has been canceled 739 * @param value the final value when the animation stopped 740 * @param velocity the final velocity when the animation stopped 741 */ onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, float velocity)742 void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, 743 float velocity); 744 } 745 746 /** 747 * Implementors of this interface can add themselves as update listeners 748 * to an <code>DynamicAnimation</code> instance to receive callbacks on every animation 749 * frame, after the current frame's values have been calculated for that 750 * <code>DynamicAnimation</code>. 751 */ 752 public interface OnAnimationUpdateListener { 753 754 /** 755 * Notifies the occurrence of another frame of the animation. 756 * 757 * @param animation animation that the update listener is added to 758 * @param value the current value of the animation 759 * @param velocity the current velocity of the animation 760 */ onAnimationUpdate(DynamicAnimation animation, float value, float velocity)761 void onAnimationUpdate(DynamicAnimation animation, float value, float velocity); 762 } 763 } 764