ViewPropertyAnimator.java revision 9d1992deaeb3d60d5928f05b649a2cc654ba98a3
1/* 2 * Copyright (C) 2011 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; 18 19import android.animation.Animator; 20import android.animation.ValueAnimator; 21import android.animation.TimeInterpolator; 22 23import java.util.ArrayList; 24import java.util.HashMap; 25import java.util.Set; 26 27/** 28 * This class enables automatic and optimized animation of select properties on View objects. 29 * If only one or two properties on a View object are being animated, then using an 30 * {@link android.animation.ObjectAnimator} is fine; the property setters called by ObjectAnimator 31 * are well equipped to do the right thing to set the property and invalidate the view 32 * appropriately. But if several properties are animated simultaneously, or if you just want a 33 * more convenient syntax to animate a specific property, then ViewPropertyAnimator might be 34 * more well-suited to the task. 35 * 36 * <p>This class may provide better performance for several simultaneous animations, because 37 * it will optimize invalidate calls to take place only once for several properties instead of each 38 * animated property independently causing its own invalidation. Also, the syntax of using this 39 * class could be easier to use because the caller need only tell the View object which 40 * property to animate, and the value to animate either to or by, and this class handles the 41 * details of configuring the underlying Animator class and starting it.</p> 42 * 43 * <p>This class is not constructed by the caller, but rather by the View whose properties 44 * it will animate. Calls to {@link android.view.View#animate()} will return a reference 45 * to the appropriate ViewPropertyAnimator object for that View.</p> 46 * 47 */ 48public class ViewPropertyAnimator { 49 50 /** 51 * The View whose properties are being animated by this class. This is set at 52 * construction time. 53 */ 54 private final View mView; 55 56 /** 57 * The duration of the underlying Animator object. By default, we don't set the duration 58 * on the Animator and just use its default duration. If the duration is ever set on this 59 * Animator, then we use the duration that it was set to. 60 */ 61 private long mDuration; 62 63 /** 64 * A flag indicating whether the duration has been set on this object. If not, we don't set 65 * the duration on the underlying Animator, but instead just use its default duration. 66 */ 67 private boolean mDurationSet = false; 68 69 /** 70 * The startDelay of the underlying Animator object. By default, we don't set the startDelay 71 * on the Animator and just use its default startDelay. If the startDelay is ever set on this 72 * Animator, then we use the startDelay that it was set to. 73 */ 74 private long mStartDelay = 0; 75 76 /** 77 * A flag indicating whether the startDelay has been set on this object. If not, we don't set 78 * the startDelay on the underlying Animator, but instead just use its default startDelay. 79 */ 80 private boolean mStartDelaySet = false; 81 82 /** 83 * The interpolator of the underlying Animator object. By default, we don't set the interpolator 84 * on the Animator and just use its default interpolator. If the interpolator is ever set on 85 * this Animator, then we use the interpolator that it was set to. 86 */ 87 private TimeInterpolator mInterpolator; 88 89 /** 90 * A flag indicating whether the interpolator has been set on this object. If not, we don't set 91 * the interpolator on the underlying Animator, but instead just use its default interpolator. 92 */ 93 private boolean mInterpolatorSet = false; 94 95 /** 96 * Listener for the lifecycle events of the underlying 97 */ 98 private Animator.AnimatorListener mListener = null; 99 100 /** 101 * This listener is the mechanism by which the underlying Animator causes changes to the 102 * properties currently being animated, as well as the cleanup after an animation is 103 * complete. 104 */ 105 private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener(); 106 107 /** 108 * This list holds the properties that have been asked to animate. We allow the caller to 109 * request several animations prior to actually starting the underlying animator. This 110 * enables us to run one single animator to handle several properties in parallel. Each 111 * property is tossed onto the pending list until the animation actually starts (which is 112 * done by posting it onto mView), at which time the pending list is cleared and the properties 113 * on that list are added to the list of properties associated with that animator. 114 */ 115 ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>(); 116 private Runnable mPendingSetupAction; 117 private Runnable mPendingCleanupAction; 118 private Runnable mPendingOnStartAction; 119 private Runnable mPendingOnEndAction; 120 121 /** 122 * Constants used to associate a property being requested and the mechanism used to set 123 * the property (this class calls directly into View to set the properties in question). 124 */ 125 private static final int NONE = 0x0000; 126 private static final int TRANSLATION_X = 0x0001; 127 private static final int TRANSLATION_Y = 0x0002; 128 private static final int SCALE_X = 0x0004; 129 private static final int SCALE_Y = 0x0008; 130 private static final int ROTATION = 0x0010; 131 private static final int ROTATION_X = 0x0020; 132 private static final int ROTATION_Y = 0x0040; 133 private static final int X = 0x0080; 134 private static final int Y = 0x0100; 135 private static final int ALPHA = 0x0200; 136 137 private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | SCALE_X | SCALE_Y | 138 ROTATION | ROTATION_X | ROTATION_Y | X | Y; 139 140 /** 141 * The mechanism by which the user can request several properties that are then animated 142 * together works by posting this Runnable to start the underlying Animator. Every time 143 * a property animation is requested, we cancel any previous postings of the Runnable 144 * and re-post it. This means that we will only ever run the Runnable (and thus start the 145 * underlying animator) after the caller is done setting the properties that should be 146 * animated together. 147 */ 148 private Runnable mAnimationStarter = new Runnable() { 149 @Override 150 public void run() { 151 startAnimation(); 152 } 153 }; 154 155 /** 156 * This class holds information about the overall animation being run on the set of 157 * properties. The mask describes which properties are being animated and the 158 * values holder is the list of all property/value objects. 159 */ 160 private static class PropertyBundle { 161 int mPropertyMask; 162 ArrayList<NameValuesHolder> mNameValuesHolder; 163 164 PropertyBundle(int propertyMask, ArrayList<NameValuesHolder> nameValuesHolder) { 165 mPropertyMask = propertyMask; 166 mNameValuesHolder = nameValuesHolder; 167 } 168 169 /** 170 * Removes the given property from being animated as a part of this 171 * PropertyBundle. If the property was a part of this bundle, it returns 172 * true to indicate that it was, in fact, canceled. This is an indication 173 * to the caller that a cancellation actually occurred. 174 * 175 * @param propertyConstant The property whose cancellation is requested. 176 * @return true if the given property is a part of this bundle and if it 177 * has therefore been canceled. 178 */ 179 boolean cancel(int propertyConstant) { 180 if ((mPropertyMask & propertyConstant) != 0 && mNameValuesHolder != null) { 181 int count = mNameValuesHolder.size(); 182 for (int i = 0; i < count; ++i) { 183 NameValuesHolder nameValuesHolder = mNameValuesHolder.get(i); 184 if (nameValuesHolder.mNameConstant == propertyConstant) { 185 mNameValuesHolder.remove(i); 186 mPropertyMask &= ~propertyConstant; 187 return true; 188 } 189 } 190 } 191 return false; 192 } 193 } 194 195 /** 196 * This list tracks the list of properties being animated by any particular animator. 197 * In most situations, there would only ever be one animator running at a time. But it is 198 * possible to request some properties to animate together, then while those properties 199 * are animating, to request some other properties to animate together. The way that 200 * works is by having this map associate the group of properties being animated with the 201 * animator handling the animation. On every update event for an Animator, we ask the 202 * map for the associated properties and set them accordingly. 203 */ 204 private HashMap<Animator, PropertyBundle> mAnimatorMap = 205 new HashMap<Animator, PropertyBundle>(); 206 private HashMap<Animator, Runnable> mAnimatorSetupMap; 207 private HashMap<Animator, Runnable> mAnimatorCleanupMap; 208 private HashMap<Animator, Runnable> mAnimatorOnStartMap; 209 private HashMap<Animator, Runnable> mAnimatorOnEndMap; 210 211 /** 212 * This is the information we need to set each property during the animation. 213 * mNameConstant is used to set the appropriate field in View, and the from/delta 214 * values are used to calculate the animated value for a given animation fraction 215 * during the animation. 216 */ 217 private static class NameValuesHolder { 218 int mNameConstant; 219 float mFromValue; 220 float mDeltaValue; 221 NameValuesHolder(int nameConstant, float fromValue, float deltaValue) { 222 mNameConstant = nameConstant; 223 mFromValue = fromValue; 224 mDeltaValue = deltaValue; 225 } 226 } 227 228 /** 229 * Constructor, called by View. This is private by design, as the user should only 230 * get a ViewPropertyAnimator by calling View.animate(). 231 * 232 * @param view The View associated with this ViewPropertyAnimator 233 */ 234 ViewPropertyAnimator(View view) { 235 mView = view; 236 view.ensureTransformationInfo(); 237 } 238 239 /** 240 * Sets the duration for the underlying animator that animates the requested properties. 241 * By default, the animator uses the default value for ValueAnimator. Calling this method 242 * will cause the declared value to be used instead. 243 * @param duration The length of ensuing property animations, in milliseconds. The value 244 * cannot be negative. 245 * @return This object, allowing calls to methods in this class to be chained. 246 */ 247 public ViewPropertyAnimator setDuration(long duration) { 248 if (duration < 0) { 249 throw new IllegalArgumentException("Animators cannot have negative duration: " + 250 duration); 251 } 252 mDurationSet = true; 253 mDuration = duration; 254 return this; 255 } 256 257 /** 258 * Returns the current duration of property animations. If the duration was set on this 259 * object, that value is returned. Otherwise, the default value of the underlying Animator 260 * is returned. 261 * 262 * @see #setDuration(long) 263 * @return The duration of animations, in milliseconds. 264 */ 265 public long getDuration() { 266 if (mDurationSet) { 267 return mDuration; 268 } else { 269 // Just return the default from ValueAnimator, since that's what we'd get if 270 // the value has not been set otherwise 271 return new ValueAnimator().getDuration(); 272 } 273 } 274 275 /** 276 * Returns the current startDelay of property animations. If the startDelay was set on this 277 * object, that value is returned. Otherwise, the default value of the underlying Animator 278 * is returned. 279 * 280 * @see #setStartDelay(long) 281 * @return The startDelay of animations, in milliseconds. 282 */ 283 public long getStartDelay() { 284 if (mStartDelaySet) { 285 return mStartDelay; 286 } else { 287 // Just return the default from ValueAnimator (0), since that's what we'd get if 288 // the value has not been set otherwise 289 return 0; 290 } 291 } 292 293 /** 294 * Sets the startDelay for the underlying animator that animates the requested properties. 295 * By default, the animator uses the default value for ValueAnimator. Calling this method 296 * will cause the declared value to be used instead. 297 * @param startDelay The delay of ensuing property animations, in milliseconds. The value 298 * cannot be negative. 299 * @return This object, allowing calls to methods in this class to be chained. 300 */ 301 public ViewPropertyAnimator setStartDelay(long startDelay) { 302 if (startDelay < 0) { 303 throw new IllegalArgumentException("Animators cannot have negative duration: " + 304 startDelay); 305 } 306 mStartDelaySet = true; 307 mStartDelay = startDelay; 308 return this; 309 } 310 311 /** 312 * Sets the interpolator for the underlying animator that animates the requested properties. 313 * By default, the animator uses the default interpolator for ValueAnimator. Calling this method 314 * will cause the declared object to be used instead. 315 * 316 * @param interpolator The TimeInterpolator to be used for ensuing property animations. 317 * @return This object, allowing calls to methods in this class to be chained. 318 */ 319 public ViewPropertyAnimator setInterpolator(TimeInterpolator interpolator) { 320 mInterpolatorSet = true; 321 mInterpolator = interpolator; 322 return this; 323 } 324 325 /** 326 * Sets a listener for events in the underlying Animators that run the property 327 * animations. 328 * 329 * @param listener The listener to be called with AnimatorListener events. 330 * @return This object, allowing calls to methods in this class to be chained. 331 */ 332 public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) { 333 mListener = listener; 334 return this; 335 } 336 337 /** 338 * Starts the currently pending property animations immediately. Calling <code>start()</code> 339 * is optional because all animations start automatically at the next opportunity. However, 340 * if the animations are needed to start immediately and synchronously (not at the time when 341 * the next event is processed by the hierarchy, which is when the animations would begin 342 * otherwise), then this method can be used. 343 */ 344 public void start() { 345 startAnimation(); 346 } 347 348 /** 349 * Cancels all property animations that are currently running or pending. 350 */ 351 public void cancel() { 352 if (mAnimatorMap.size() > 0) { 353 HashMap<Animator, PropertyBundle> mAnimatorMapCopy = 354 (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone(); 355 Set<Animator> animatorSet = mAnimatorMapCopy.keySet(); 356 for (Animator runningAnim : animatorSet) { 357 runningAnim.cancel(); 358 } 359 } 360 mPendingAnimations.clear(); 361 mView.removeCallbacks(mAnimationStarter); 362 } 363 364 /** 365 * This method will cause the View's <code>x</code> property to be animated to the 366 * specified value. Animations already running on the property will be canceled. 367 * 368 * @param value The value to be animated to. 369 * @see View#setX(float) 370 * @return This object, allowing calls to methods in this class to be chained. 371 */ 372 public ViewPropertyAnimator x(float value) { 373 animateProperty(X, value); 374 return this; 375 } 376 377 /** 378 * This method will cause the View's <code>x</code> property to be animated by the 379 * specified value. Animations already running on the property will be canceled. 380 * 381 * @param value The amount to be animated by, as an offset from the current value. 382 * @see View#setX(float) 383 * @return This object, allowing calls to methods in this class to be chained. 384 */ 385 public ViewPropertyAnimator xBy(float value) { 386 animatePropertyBy(X, value); 387 return this; 388 } 389 390 /** 391 * This method will cause the View's <code>y</code> property to be animated to the 392 * specified value. Animations already running on the property will be canceled. 393 * 394 * @param value The value to be animated to. 395 * @see View#setY(float) 396 * @return This object, allowing calls to methods in this class to be chained. 397 */ 398 public ViewPropertyAnimator y(float value) { 399 animateProperty(Y, value); 400 return this; 401 } 402 403 /** 404 * This method will cause the View's <code>y</code> property to be animated by the 405 * specified value. Animations already running on the property will be canceled. 406 * 407 * @param value The amount to be animated by, as an offset from the current value. 408 * @see View#setY(float) 409 * @return This object, allowing calls to methods in this class to be chained. 410 */ 411 public ViewPropertyAnimator yBy(float value) { 412 animatePropertyBy(Y, value); 413 return this; 414 } 415 416 /** 417 * This method will cause the View's <code>rotation</code> property to be animated to the 418 * specified value. Animations already running on the property will be canceled. 419 * 420 * @param value The value to be animated to. 421 * @see View#setRotation(float) 422 * @return This object, allowing calls to methods in this class to be chained. 423 */ 424 public ViewPropertyAnimator rotation(float value) { 425 animateProperty(ROTATION, value); 426 return this; 427 } 428 429 /** 430 * This method will cause the View's <code>rotation</code> property to be animated by the 431 * specified value. Animations already running on the property will be canceled. 432 * 433 * @param value The amount to be animated by, as an offset from the current value. 434 * @see View#setRotation(float) 435 * @return This object, allowing calls to methods in this class to be chained. 436 */ 437 public ViewPropertyAnimator rotationBy(float value) { 438 animatePropertyBy(ROTATION, value); 439 return this; 440 } 441 442 /** 443 * This method will cause the View's <code>rotationX</code> property to be animated to the 444 * specified value. Animations already running on the property will be canceled. 445 * 446 * @param value The value to be animated to. 447 * @see View#setRotationX(float) 448 * @return This object, allowing calls to methods in this class to be chained. 449 */ 450 public ViewPropertyAnimator rotationX(float value) { 451 animateProperty(ROTATION_X, value); 452 return this; 453 } 454 455 /** 456 * This method will cause the View's <code>rotationX</code> property to be animated by the 457 * specified value. Animations already running on the property will be canceled. 458 * 459 * @param value The amount to be animated by, as an offset from the current value. 460 * @see View#setRotationX(float) 461 * @return This object, allowing calls to methods in this class to be chained. 462 */ 463 public ViewPropertyAnimator rotationXBy(float value) { 464 animatePropertyBy(ROTATION_X, value); 465 return this; 466 } 467 468 /** 469 * This method will cause the View's <code>rotationY</code> property to be animated to the 470 * specified value. Animations already running on the property will be canceled. 471 * 472 * @param value The value to be animated to. 473 * @see View#setRotationY(float) 474 * @return This object, allowing calls to methods in this class to be chained. 475 */ 476 public ViewPropertyAnimator rotationY(float value) { 477 animateProperty(ROTATION_Y, value); 478 return this; 479 } 480 481 /** 482 * This method will cause the View's <code>rotationY</code> property to be animated by the 483 * specified value. Animations already running on the property will be canceled. 484 * 485 * @param value The amount to be animated by, as an offset from the current value. 486 * @see View#setRotationY(float) 487 * @return This object, allowing calls to methods in this class to be chained. 488 */ 489 public ViewPropertyAnimator rotationYBy(float value) { 490 animatePropertyBy(ROTATION_Y, value); 491 return this; 492 } 493 494 /** 495 * This method will cause the View's <code>translationX</code> property to be animated to the 496 * specified value. Animations already running on the property will be canceled. 497 * 498 * @param value The value to be animated to. 499 * @see View#setTranslationX(float) 500 * @return This object, allowing calls to methods in this class to be chained. 501 */ 502 public ViewPropertyAnimator translationX(float value) { 503 animateProperty(TRANSLATION_X, value); 504 return this; 505 } 506 507 /** 508 * This method will cause the View's <code>translationX</code> property to be animated by the 509 * specified value. Animations already running on the property will be canceled. 510 * 511 * @param value The amount to be animated by, as an offset from the current value. 512 * @see View#setTranslationX(float) 513 * @return This object, allowing calls to methods in this class to be chained. 514 */ 515 public ViewPropertyAnimator translationXBy(float value) { 516 animatePropertyBy(TRANSLATION_X, value); 517 return this; 518 } 519 520 /** 521 * This method will cause the View's <code>translationY</code> property to be animated to the 522 * specified value. Animations already running on the property will be canceled. 523 * 524 * @param value The value to be animated to. 525 * @see View#setTranslationY(float) 526 * @return This object, allowing calls to methods in this class to be chained. 527 */ 528 public ViewPropertyAnimator translationY(float value) { 529 animateProperty(TRANSLATION_Y, value); 530 return this; 531 } 532 533 /** 534 * This method will cause the View's <code>translationY</code> property to be animated by the 535 * specified value. Animations already running on the property will be canceled. 536 * 537 * @param value The amount to be animated by, as an offset from the current value. 538 * @see View#setTranslationY(float) 539 * @return This object, allowing calls to methods in this class to be chained. 540 */ 541 public ViewPropertyAnimator translationYBy(float value) { 542 animatePropertyBy(TRANSLATION_Y, value); 543 return this; 544 } 545 546 /** 547 * This method will cause the View's <code>scaleX</code> property to be animated to the 548 * specified value. Animations already running on the property will be canceled. 549 * 550 * @param value The value to be animated to. 551 * @see View#setScaleX(float) 552 * @return This object, allowing calls to methods in this class to be chained. 553 */ 554 public ViewPropertyAnimator scaleX(float value) { 555 animateProperty(SCALE_X, value); 556 return this; 557 } 558 559 /** 560 * This method will cause the View's <code>scaleX</code> property to be animated by the 561 * specified value. Animations already running on the property will be canceled. 562 * 563 * @param value The amount to be animated by, as an offset from the current value. 564 * @see View#setScaleX(float) 565 * @return This object, allowing calls to methods in this class to be chained. 566 */ 567 public ViewPropertyAnimator scaleXBy(float value) { 568 animatePropertyBy(SCALE_X, value); 569 return this; 570 } 571 572 /** 573 * This method will cause the View's <code>scaleY</code> property to be animated to the 574 * specified value. Animations already running on the property will be canceled. 575 * 576 * @param value The value to be animated to. 577 * @see View#setScaleY(float) 578 * @return This object, allowing calls to methods in this class to be chained. 579 */ 580 public ViewPropertyAnimator scaleY(float value) { 581 animateProperty(SCALE_Y, value); 582 return this; 583 } 584 585 /** 586 * This method will cause the View's <code>scaleY</code> property to be animated by the 587 * specified value. Animations already running on the property will be canceled. 588 * 589 * @param value The amount to be animated by, as an offset from the current value. 590 * @see View#setScaleY(float) 591 * @return This object, allowing calls to methods in this class to be chained. 592 */ 593 public ViewPropertyAnimator scaleYBy(float value) { 594 animatePropertyBy(SCALE_Y, value); 595 return this; 596 } 597 598 /** 599 * This method will cause the View's <code>alpha</code> property to be animated to the 600 * specified value. Animations already running on the property will be canceled. 601 * 602 * @param value The value to be animated to. 603 * @see View#setAlpha(float) 604 * @return This object, allowing calls to methods in this class to be chained. 605 */ 606 public ViewPropertyAnimator alpha(float value) { 607 animateProperty(ALPHA, value); 608 return this; 609 } 610 611 /** 612 * This method will cause the View's <code>alpha</code> property to be animated by the 613 * specified value. Animations already running on the property will be canceled. 614 * 615 * @param value The amount to be animated by, as an offset from the current value. 616 * @see View#setAlpha(float) 617 * @return This object, allowing calls to methods in this class to be chained. 618 */ 619 public ViewPropertyAnimator alphaBy(float value) { 620 animatePropertyBy(ALPHA, value); 621 return this; 622 } 623 624 /** 625 * The View associated with this ViewPropertyAnimator will have its 626 * {@link View#setLayerType(int, android.graphics.Paint) layer type} set to 627 * {@link View#LAYER_TYPE_HARDWARE} for the duration of the next animation. This state 628 * is not persistent, either on the View or on this ViewPropertyAnimator: the layer type 629 * of the View will be restored when the animation ends to what it was when this method was 630 * called, and this setting on ViewPropertyAnimator is only valid for the next animation. 631 * Note that calling this method and then independently setting the layer type of the View 632 * (by a direct call to {@link View#setLayerType(int, android.graphics.Paint)}) will result 633 * in some inconsistency, including having the layer type restored to its pre-withLayer() 634 * value when the animation ends. 635 * 636 * @see View#setLayerType(int, android.graphics.Paint) 637 * @return This object, allowing calls to methods in this class to be chained. 638 */ 639 public ViewPropertyAnimator withLayer() { 640 mPendingSetupAction= new Runnable() { 641 @Override 642 public void run() { 643 mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 644 } 645 }; 646 final int currentLayerType = mView.getLayerType(); 647 mPendingCleanupAction = new Runnable() { 648 @Override 649 public void run() { 650 mView.setLayerType(currentLayerType, null); 651 } 652 }; 653 if (mAnimatorSetupMap == null) { 654 mAnimatorSetupMap = new HashMap<Animator, Runnable>(); 655 } 656 if (mAnimatorCleanupMap == null) { 657 mAnimatorCleanupMap = new HashMap<Animator, Runnable>(); 658 } 659 660 return this; 661 } 662 663 /** 664 * Specifies an action to take place when the next animation runs. If there is a 665 * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the 666 * action will run after that startDelay expires, when the actual animation begins. 667 * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate 668 * choreographing ViewPropertyAnimator animations with other animations or actions 669 * in the application. 670 * 671 * @param runnable The action to run when the next animation starts. 672 * @return This object, allowing calls to methods in this class to be chained. 673 */ 674 public ViewPropertyAnimator withStartAction(Runnable runnable) { 675 mPendingOnStartAction = runnable; 676 if (runnable != null && mAnimatorOnStartMap == null) { 677 mAnimatorOnStartMap = new HashMap<Animator, Runnable>(); 678 } 679 return this; 680 } 681 682 /** 683 * Specifies an action to take place when the next animation ends. The action is only 684 * run if the animation ends normally; if the ViewPropertyAnimator is canceled during 685 * that animation, the runnable will not run. 686 * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate 687 * choreographing ViewPropertyAnimator animations with other animations or actions 688 * in the application. 689 * 690 * <p>For example, the following code animates a view to x=200 and then back to 0:</p> 691 * <pre> 692 * Runnable endAction = new Runnable() { 693 * public void run() { 694 * view.animate().x(0); 695 * } 696 * }; 697 * view.animate().x(200).onEnd(endAction); 698 * </pre> 699 * 700 * @param runnable The action to run when the next animation ends. 701 * @return This object, allowing calls to methods in this class to be chained. 702 */ 703 public ViewPropertyAnimator withEndAction(Runnable runnable) { 704 mPendingOnEndAction = runnable; 705 if (runnable != null && mAnimatorOnEndMap == null) { 706 mAnimatorOnEndMap = new HashMap<Animator, Runnable>(); 707 } 708 return this; 709 } 710 711 /** 712 * Starts the underlying Animator for a set of properties. We use a single animator that 713 * simply runs from 0 to 1, and then use that fractional value to set each property 714 * value accordingly. 715 */ 716 private void startAnimation() { 717 ValueAnimator animator = ValueAnimator.ofFloat(1.0f); 718 ArrayList<NameValuesHolder> nameValueList = 719 (ArrayList<NameValuesHolder>) mPendingAnimations.clone(); 720 mPendingAnimations.clear(); 721 int propertyMask = 0; 722 int propertyCount = nameValueList.size(); 723 for (int i = 0; i < propertyCount; ++i) { 724 NameValuesHolder nameValuesHolder = nameValueList.get(i); 725 propertyMask |= nameValuesHolder.mNameConstant; 726 } 727 mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList)); 728 if (mPendingSetupAction != null) { 729 mAnimatorSetupMap.put(animator, mPendingSetupAction); 730 mPendingSetupAction = null; 731 } 732 if (mPendingCleanupAction != null) { 733 mAnimatorCleanupMap.put(animator, mPendingCleanupAction); 734 mPendingCleanupAction = null; 735 } 736 if (mPendingOnStartAction != null) { 737 mAnimatorOnStartMap.put(animator, mPendingOnStartAction); 738 mPendingOnStartAction = null; 739 } 740 if (mPendingOnEndAction != null) { 741 mAnimatorOnEndMap.put(animator, mPendingOnEndAction); 742 mPendingOnEndAction = null; 743 } 744 animator.addUpdateListener(mAnimatorEventListener); 745 animator.addListener(mAnimatorEventListener); 746 if (mStartDelaySet) { 747 animator.setStartDelay(mStartDelay); 748 } 749 if (mDurationSet) { 750 animator.setDuration(mDuration); 751 } 752 if (mInterpolatorSet) { 753 animator.setInterpolator(mInterpolator); 754 } 755 animator.start(); 756 } 757 758 /** 759 * Utility function, called by the various x(), y(), etc. methods. This stores the 760 * constant name for the property along with the from/delta values that will be used to 761 * calculate and set the property during the animation. This structure is added to the 762 * pending animations, awaiting the eventual start() of the underlying animator. A 763 * Runnable is posted to start the animation, and any pending such Runnable is canceled 764 * (which enables us to end up starting just one animator for all of the properties 765 * specified at one time). 766 * 767 * @param constantName The specifier for the property being animated 768 * @param toValue The value to which the property will animate 769 */ 770 private void animateProperty(int constantName, float toValue) { 771 float fromValue = getValue(constantName); 772 float deltaValue = toValue - fromValue; 773 animatePropertyBy(constantName, fromValue, deltaValue); 774 } 775 776 /** 777 * Utility function, called by the various xBy(), yBy(), etc. methods. This method is 778 * just like animateProperty(), except the value is an offset from the property's 779 * current value, instead of an absolute "to" value. 780 * 781 * @param constantName The specifier for the property being animated 782 * @param byValue The amount by which the property will change 783 */ 784 private void animatePropertyBy(int constantName, float byValue) { 785 float fromValue = getValue(constantName); 786 animatePropertyBy(constantName, fromValue, byValue); 787 } 788 789 /** 790 * Utility function, called by animateProperty() and animatePropertyBy(), which handles the 791 * details of adding a pending animation and posting the request to start the animation. 792 * 793 * @param constantName The specifier for the property being animated 794 * @param startValue The starting value of the property 795 * @param byValue The amount by which the property will change 796 */ 797 private void animatePropertyBy(int constantName, float startValue, float byValue) { 798 // First, cancel any existing animations on this property 799 if (mAnimatorMap.size() > 0) { 800 Animator animatorToCancel = null; 801 Set<Animator> animatorSet = mAnimatorMap.keySet(); 802 for (Animator runningAnim : animatorSet) { 803 PropertyBundle bundle = mAnimatorMap.get(runningAnim); 804 if (bundle.cancel(constantName)) { 805 // property was canceled - cancel the animation if it's now empty 806 // Note that it's safe to break out here because every new animation 807 // on a property will cancel a previous animation on that property, so 808 // there can only ever be one such animation running. 809 if (bundle.mPropertyMask == NONE) { 810 // the animation is no longer changing anything - cancel it 811 animatorToCancel = runningAnim; 812 break; 813 } 814 } 815 } 816 if (animatorToCancel != null) { 817 animatorToCancel.cancel(); 818 } 819 } 820 821 NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue); 822 mPendingAnimations.add(nameValuePair); 823 mView.removeCallbacks(mAnimationStarter); 824 mView.post(mAnimationStarter); 825 } 826 827 /** 828 * This method handles setting the property values directly in the View object's fields. 829 * propertyConstant tells it which property should be set, value is the value to set 830 * the property to. 831 * 832 * @param propertyConstant The property to be set 833 * @param value The value to set the property to 834 */ 835 private void setValue(int propertyConstant, float value) { 836 final View.TransformationInfo info = mView.mTransformationInfo; 837 switch (propertyConstant) { 838 case TRANSLATION_X: 839 info.mTranslationX = value; 840 break; 841 case TRANSLATION_Y: 842 info.mTranslationY = value; 843 break; 844 case ROTATION: 845 info.mRotation = value; 846 break; 847 case ROTATION_X: 848 info.mRotationX = value; 849 break; 850 case ROTATION_Y: 851 info.mRotationY = value; 852 break; 853 case SCALE_X: 854 info.mScaleX = value; 855 break; 856 case SCALE_Y: 857 info.mScaleY = value; 858 break; 859 case X: 860 info.mTranslationX = value - mView.mLeft; 861 break; 862 case Y: 863 info.mTranslationY = value - mView.mTop; 864 break; 865 case ALPHA: 866 info.mAlpha = value; 867 break; 868 } 869 // TODO: optimize to set only the properties that have changed 870 mView.setDisplayListProperties(); 871 } 872 873 /** 874 * This method gets the value of the named property from the View object. 875 * 876 * @param propertyConstant The property whose value should be returned 877 * @return float The value of the named property 878 */ 879 private float getValue(int propertyConstant) { 880 final View.TransformationInfo info = mView.mTransformationInfo; 881 switch (propertyConstant) { 882 case TRANSLATION_X: 883 return info.mTranslationX; 884 case TRANSLATION_Y: 885 return info.mTranslationY; 886 case ROTATION: 887 return info.mRotation; 888 case ROTATION_X: 889 return info.mRotationX; 890 case ROTATION_Y: 891 return info.mRotationY; 892 case SCALE_X: 893 return info.mScaleX; 894 case SCALE_Y: 895 return info.mScaleY; 896 case X: 897 return mView.mLeft + info.mTranslationX; 898 case Y: 899 return mView.mTop + info.mTranslationY; 900 case ALPHA: 901 return info.mAlpha; 902 } 903 return 0; 904 } 905 906 /** 907 * Utility class that handles the various Animator events. The only ones we care 908 * about are the end event (which we use to clean up the animator map when an animator 909 * finishes) and the update event (which we use to calculate the current value of each 910 * property and then set it on the view object). 911 */ 912 private class AnimatorEventListener 913 implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener { 914 @Override 915 public void onAnimationStart(Animator animation) { 916 if (mAnimatorSetupMap != null) { 917 Runnable r = mAnimatorSetupMap.get(animation); 918 if (r != null) { 919 r.run(); 920 } 921 mAnimatorSetupMap.remove(animation); 922 } 923 if (mAnimatorOnStartMap != null) { 924 Runnable r = mAnimatorOnStartMap.get(animation); 925 if (r != null) { 926 r.run(); 927 } 928 mAnimatorOnStartMap.remove(animation); 929 } 930 if (mListener != null) { 931 mListener.onAnimationStart(animation); 932 } 933 } 934 935 @Override 936 public void onAnimationCancel(Animator animation) { 937 if (mListener != null) { 938 mListener.onAnimationCancel(animation); 939 } 940 if (mAnimatorOnEndMap != null) { 941 mAnimatorOnEndMap.remove(animation); 942 } 943 } 944 945 @Override 946 public void onAnimationRepeat(Animator animation) { 947 if (mListener != null) { 948 mListener.onAnimationRepeat(animation); 949 } 950 } 951 952 @Override 953 public void onAnimationEnd(Animator animation) { 954 if (mListener != null) { 955 mListener.onAnimationEnd(animation); 956 } 957 if (mAnimatorOnEndMap != null) { 958 Runnable r = mAnimatorOnEndMap.get(animation); 959 if (r != null) { 960 r.run(); 961 } 962 mAnimatorOnEndMap.remove(animation); 963 } 964 if (mAnimatorCleanupMap != null) { 965 Runnable r = mAnimatorCleanupMap.get(animation); 966 if (r != null) { 967 r.run(); 968 } 969 mAnimatorCleanupMap.remove(animation); 970 } 971 mAnimatorMap.remove(animation); 972 } 973 974 /** 975 * Calculate the current value for each property and set it on the view. Invalidate 976 * the view object appropriately, depending on which properties are being animated. 977 * 978 * @param animation The animator associated with the properties that need to be 979 * set. This animator holds the animation fraction which we will use to calculate 980 * the current value of each property. 981 */ 982 @Override 983 public void onAnimationUpdate(ValueAnimator animation) { 984 PropertyBundle propertyBundle = mAnimatorMap.get(animation); 985 if (propertyBundle == null) { 986 // Shouldn't happen, but just to play it safe 987 return; 988 } 989 boolean useDisplayListProperties = View.USE_DISPLAY_LIST_PROPERTIES && 990 mView.mDisplayList != null; 991 992 // alpha requires slightly different treatment than the other (transform) properties. 993 // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation 994 // logic is dependent on how the view handles an internal call to onSetAlpha(). 995 // We track what kinds of properties are set, and how alpha is handled when it is 996 // set, and perform the invalidation steps appropriately. 997 boolean alphaHandled = false; 998 if (!useDisplayListProperties) { 999 mView.invalidateParentCaches(); 1000 } 1001 float fraction = animation.getAnimatedFraction(); 1002 int propertyMask = propertyBundle.mPropertyMask; 1003 if ((propertyMask & TRANSFORM_MASK) != 0) { 1004 mView.invalidateViewProperty(false, false); 1005 } 1006 ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder; 1007 if (valueList != null) { 1008 int count = valueList.size(); 1009 for (int i = 0; i < count; ++i) { 1010 NameValuesHolder values = valueList.get(i); 1011 float value = values.mFromValue + fraction * values.mDeltaValue; 1012 if (values.mNameConstant == ALPHA) { 1013 alphaHandled = mView.setAlphaNoInvalidation(value); 1014 } else { 1015 setValue(values.mNameConstant, value); 1016 } 1017 } 1018 } 1019 if ((propertyMask & TRANSFORM_MASK) != 0) { 1020 mView.mTransformationInfo.mMatrixDirty = true; 1021 if (!useDisplayListProperties) { 1022 mView.mPrivateFlags |= View.DRAWN; // force another invalidation 1023 } 1024 } 1025 // invalidate(false) in all cases except if alphaHandled gets set to true 1026 // via the call to setAlphaNoInvalidation(), above 1027 if (alphaHandled) { 1028 mView.invalidate(true); 1029 } else { 1030 mView.invalidateViewProperty(false, false); 1031 } 1032 } 1033 } 1034} 1035