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