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 17 package android.view; 18 19 import android.animation.Animator; 20 import android.animation.ValueAnimator; 21 import android.animation.TimeInterpolator; 22 23 import java.util.ArrayList; 24 import java.util.HashMap; 25 import 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 */ 48 public 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 * Returns the timing interpolator that this animation uses. 327 * 328 * @return The timing interpolator for this animation. 329 */ 330 public TimeInterpolator getInterpolator() { 331 return null; 332 } 333 334 /** 335 * Sets a listener for events in the underlying Animators that run the property 336 * animations. 337 * 338 * @param listener The listener to be called with AnimatorListener events. 339 * @return This object, allowing calls to methods in this class to be chained. 340 */ 341 public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) { 342 mListener = listener; 343 return this; 344 } 345 346 /** 347 * Starts the currently pending property animations immediately. Calling <code>start()</code> 348 * is optional because all animations start automatically at the next opportunity. However, 349 * if the animations are needed to start immediately and synchronously (not at the time when 350 * the next event is processed by the hierarchy, which is when the animations would begin 351 * otherwise), then this method can be used. 352 */ 353 public void start() { 354 mView.removeCallbacks(mAnimationStarter); 355 startAnimation(); 356 } 357 358 /** 359 * Cancels all property animations that are currently running or pending. 360 */ 361 public void cancel() { 362 if (mAnimatorMap.size() > 0) { 363 HashMap<Animator, PropertyBundle> mAnimatorMapCopy = 364 (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone(); 365 Set<Animator> animatorSet = mAnimatorMapCopy.keySet(); 366 for (Animator runningAnim : animatorSet) { 367 runningAnim.cancel(); 368 } 369 } 370 mPendingAnimations.clear(); 371 mView.removeCallbacks(mAnimationStarter); 372 } 373 374 /** 375 * This method will cause the View's <code>x</code> property to be animated to the 376 * specified value. Animations already running on the property will be canceled. 377 * 378 * @param value The value to be animated to. 379 * @see View#setX(float) 380 * @return This object, allowing calls to methods in this class to be chained. 381 */ 382 public ViewPropertyAnimator x(float value) { 383 animateProperty(X, value); 384 return this; 385 } 386 387 /** 388 * This method will cause the View's <code>x</code> property to be animated by the 389 * specified value. Animations already running on the property will be canceled. 390 * 391 * @param value The amount to be animated by, as an offset from the current value. 392 * @see View#setX(float) 393 * @return This object, allowing calls to methods in this class to be chained. 394 */ 395 public ViewPropertyAnimator xBy(float value) { 396 animatePropertyBy(X, value); 397 return this; 398 } 399 400 /** 401 * This method will cause the View's <code>y</code> property to be animated to the 402 * specified value. Animations already running on the property will be canceled. 403 * 404 * @param value The value to be animated to. 405 * @see View#setY(float) 406 * @return This object, allowing calls to methods in this class to be chained. 407 */ 408 public ViewPropertyAnimator y(float value) { 409 animateProperty(Y, value); 410 return this; 411 } 412 413 /** 414 * This method will cause the View's <code>y</code> property to be animated by the 415 * specified value. Animations already running on the property will be canceled. 416 * 417 * @param value The amount to be animated by, as an offset from the current value. 418 * @see View#setY(float) 419 * @return This object, allowing calls to methods in this class to be chained. 420 */ 421 public ViewPropertyAnimator yBy(float value) { 422 animatePropertyBy(Y, value); 423 return this; 424 } 425 426 /** 427 * This method will cause the View's <code>rotation</code> property to be animated to the 428 * specified value. Animations already running on the property will be canceled. 429 * 430 * @param value The value to be animated to. 431 * @see View#setRotation(float) 432 * @return This object, allowing calls to methods in this class to be chained. 433 */ 434 public ViewPropertyAnimator rotation(float value) { 435 animateProperty(ROTATION, value); 436 return this; 437 } 438 439 /** 440 * This method will cause the View's <code>rotation</code> property to be animated by the 441 * specified value. Animations already running on the property will be canceled. 442 * 443 * @param value The amount to be animated by, as an offset from the current value. 444 * @see View#setRotation(float) 445 * @return This object, allowing calls to methods in this class to be chained. 446 */ 447 public ViewPropertyAnimator rotationBy(float value) { 448 animatePropertyBy(ROTATION, value); 449 return this; 450 } 451 452 /** 453 * This method will cause the View's <code>rotationX</code> property to be animated to the 454 * specified value. Animations already running on the property will be canceled. 455 * 456 * @param value The value to be animated to. 457 * @see View#setRotationX(float) 458 * @return This object, allowing calls to methods in this class to be chained. 459 */ 460 public ViewPropertyAnimator rotationX(float value) { 461 animateProperty(ROTATION_X, value); 462 return this; 463 } 464 465 /** 466 * This method will cause the View's <code>rotationX</code> property to be animated by the 467 * specified value. Animations already running on the property will be canceled. 468 * 469 * @param value The amount to be animated by, as an offset from the current value. 470 * @see View#setRotationX(float) 471 * @return This object, allowing calls to methods in this class to be chained. 472 */ 473 public ViewPropertyAnimator rotationXBy(float value) { 474 animatePropertyBy(ROTATION_X, value); 475 return this; 476 } 477 478 /** 479 * This method will cause the View's <code>rotationY</code> property to be animated to the 480 * specified value. Animations already running on the property will be canceled. 481 * 482 * @param value The value to be animated to. 483 * @see View#setRotationY(float) 484 * @return This object, allowing calls to methods in this class to be chained. 485 */ 486 public ViewPropertyAnimator rotationY(float value) { 487 animateProperty(ROTATION_Y, value); 488 return this; 489 } 490 491 /** 492 * This method will cause the View's <code>rotationY</code> property to be animated by the 493 * specified value. Animations already running on the property will be canceled. 494 * 495 * @param value The amount to be animated by, as an offset from the current value. 496 * @see View#setRotationY(float) 497 * @return This object, allowing calls to methods in this class to be chained. 498 */ 499 public ViewPropertyAnimator rotationYBy(float value) { 500 animatePropertyBy(ROTATION_Y, value); 501 return this; 502 } 503 504 /** 505 * This method will cause the View's <code>translationX</code> property to be animated to the 506 * specified value. Animations already running on the property will be canceled. 507 * 508 * @param value The value to be animated to. 509 * @see View#setTranslationX(float) 510 * @return This object, allowing calls to methods in this class to be chained. 511 */ 512 public ViewPropertyAnimator translationX(float value) { 513 animateProperty(TRANSLATION_X, value); 514 return this; 515 } 516 517 /** 518 * This method will cause the View's <code>translationX</code> property to be animated by the 519 * specified value. Animations already running on the property will be canceled. 520 * 521 * @param value The amount to be animated by, as an offset from the current value. 522 * @see View#setTranslationX(float) 523 * @return This object, allowing calls to methods in this class to be chained. 524 */ 525 public ViewPropertyAnimator translationXBy(float value) { 526 animatePropertyBy(TRANSLATION_X, value); 527 return this; 528 } 529 530 /** 531 * This method will cause the View's <code>translationY</code> property to be animated to the 532 * specified value. Animations already running on the property will be canceled. 533 * 534 * @param value The value to be animated to. 535 * @see View#setTranslationY(float) 536 * @return This object, allowing calls to methods in this class to be chained. 537 */ 538 public ViewPropertyAnimator translationY(float value) { 539 animateProperty(TRANSLATION_Y, value); 540 return this; 541 } 542 543 /** 544 * This method will cause the View's <code>translationY</code> property to be animated by the 545 * specified value. Animations already running on the property will be canceled. 546 * 547 * @param value The amount to be animated by, as an offset from the current value. 548 * @see View#setTranslationY(float) 549 * @return This object, allowing calls to methods in this class to be chained. 550 */ 551 public ViewPropertyAnimator translationYBy(float value) { 552 animatePropertyBy(TRANSLATION_Y, value); 553 return this; 554 } 555 556 /** 557 * This method will cause the View's <code>scaleX</code> property to be animated to the 558 * specified value. Animations already running on the property will be canceled. 559 * 560 * @param value The value to be animated to. 561 * @see View#setScaleX(float) 562 * @return This object, allowing calls to methods in this class to be chained. 563 */ 564 public ViewPropertyAnimator scaleX(float value) { 565 animateProperty(SCALE_X, value); 566 return this; 567 } 568 569 /** 570 * This method will cause the View's <code>scaleX</code> property to be animated by the 571 * specified value. Animations already running on the property will be canceled. 572 * 573 * @param value The amount to be animated by, as an offset from the current value. 574 * @see View#setScaleX(float) 575 * @return This object, allowing calls to methods in this class to be chained. 576 */ 577 public ViewPropertyAnimator scaleXBy(float value) { 578 animatePropertyBy(SCALE_X, value); 579 return this; 580 } 581 582 /** 583 * This method will cause the View's <code>scaleY</code> property to be animated to the 584 * specified value. Animations already running on the property will be canceled. 585 * 586 * @param value The value to be animated to. 587 * @see View#setScaleY(float) 588 * @return This object, allowing calls to methods in this class to be chained. 589 */ 590 public ViewPropertyAnimator scaleY(float value) { 591 animateProperty(SCALE_Y, value); 592 return this; 593 } 594 595 /** 596 * This method will cause the View's <code>scaleY</code> property to be animated by the 597 * specified value. Animations already running on the property will be canceled. 598 * 599 * @param value The amount to be animated by, as an offset from the current value. 600 * @see View#setScaleY(float) 601 * @return This object, allowing calls to methods in this class to be chained. 602 */ 603 public ViewPropertyAnimator scaleYBy(float value) { 604 animatePropertyBy(SCALE_Y, value); 605 return this; 606 } 607 608 /** 609 * This method will cause the View's <code>alpha</code> property to be animated to the 610 * specified value. Animations already running on the property will be canceled. 611 * 612 * @param value The value to be animated to. 613 * @see View#setAlpha(float) 614 * @return This object, allowing calls to methods in this class to be chained. 615 */ 616 public ViewPropertyAnimator alpha(float value) { 617 animateProperty(ALPHA, value); 618 return this; 619 } 620 621 /** 622 * This method will cause the View's <code>alpha</code> property to be animated by the 623 * specified value. Animations already running on the property will be canceled. 624 * 625 * @param value The amount to be animated by, as an offset from the current value. 626 * @see View#setAlpha(float) 627 * @return This object, allowing calls to methods in this class to be chained. 628 */ 629 public ViewPropertyAnimator alphaBy(float value) { 630 animatePropertyBy(ALPHA, value); 631 return this; 632 } 633 634 /** 635 * The View associated with this ViewPropertyAnimator will have its 636 * {@link View#setLayerType(int, android.graphics.Paint) layer type} set to 637 * {@link View#LAYER_TYPE_HARDWARE} for the duration of the next animation. 638 * As stated in the documentation for {@link View#LAYER_TYPE_HARDWARE}, 639 * the actual type of layer used internally depends on the runtime situation of the 640 * view. If the activity and this view are hardware-accelerated, then the layer will be 641 * accelerated as well. If the activity or the view is not accelerated, then the layer will 642 * effectively be the same as {@link View#LAYER_TYPE_SOFTWARE}. 643 * 644 * <p>This state is not persistent, either on the View or on this ViewPropertyAnimator: the 645 * layer type of the View will be restored when the animation ends to what it was when this 646 * method was called, and this setting on ViewPropertyAnimator is only valid for the next 647 * animation. Note that calling this method and then independently setting the layer type of 648 * the View (by a direct call to {@link View#setLayerType(int, android.graphics.Paint)}) will 649 * result in some inconsistency, including having the layer type restored to its pre-withLayer() 650 * value when the animation ends.</p> 651 * 652 * @see View#setLayerType(int, android.graphics.Paint) 653 * @return This object, allowing calls to methods in this class to be chained. 654 */ 655 public ViewPropertyAnimator withLayer() { 656 mPendingSetupAction= new Runnable() { 657 @Override 658 public void run() { 659 mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 660 } 661 }; 662 final int currentLayerType = mView.getLayerType(); 663 mPendingCleanupAction = new Runnable() { 664 @Override 665 public void run() { 666 mView.setLayerType(currentLayerType, null); 667 } 668 }; 669 if (mAnimatorSetupMap == null) { 670 mAnimatorSetupMap = new HashMap<Animator, Runnable>(); 671 } 672 if (mAnimatorCleanupMap == null) { 673 mAnimatorCleanupMap = new HashMap<Animator, Runnable>(); 674 } 675 676 return this; 677 } 678 679 /** 680 * Specifies an action to take place when the next animation runs. If there is a 681 * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the 682 * action will run after that startDelay expires, when the actual animation begins. 683 * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate 684 * choreographing ViewPropertyAnimator animations with other animations or actions 685 * in the application. 686 * 687 * @param runnable The action to run when the next animation starts. 688 * @return This object, allowing calls to methods in this class to be chained. 689 */ 690 public ViewPropertyAnimator withStartAction(Runnable runnable) { 691 mPendingOnStartAction = runnable; 692 if (runnable != null && mAnimatorOnStartMap == null) { 693 mAnimatorOnStartMap = new HashMap<Animator, Runnable>(); 694 } 695 return this; 696 } 697 698 /** 699 * Specifies an action to take place when the next animation ends. The action is only 700 * run if the animation ends normally; if the ViewPropertyAnimator is canceled during 701 * that animation, the runnable will not run. 702 * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate 703 * choreographing ViewPropertyAnimator animations with other animations or actions 704 * in the application. 705 * 706 * <p>For example, the following code animates a view to x=200 and then back to 0:</p> 707 * <pre> 708 * Runnable endAction = new Runnable() { 709 * public void run() { 710 * view.animate().x(0); 711 * } 712 * }; 713 * view.animate().x(200).withEndAction(endAction); 714 * </pre> 715 * 716 * @param runnable The action to run when the next animation ends. 717 * @return This object, allowing calls to methods in this class to be chained. 718 */ 719 public ViewPropertyAnimator withEndAction(Runnable runnable) { 720 mPendingOnEndAction = runnable; 721 if (runnable != null && mAnimatorOnEndMap == null) { 722 mAnimatorOnEndMap = new HashMap<Animator, Runnable>(); 723 } 724 return this; 725 } 726 727 /** 728 * Starts the underlying Animator for a set of properties. We use a single animator that 729 * simply runs from 0 to 1, and then use that fractional value to set each property 730 * value accordingly. 731 */ 732 private void startAnimation() { 733 mView.setHasTransientState(true); 734 ValueAnimator animator = ValueAnimator.ofFloat(1.0f); 735 ArrayList<NameValuesHolder> nameValueList = 736 (ArrayList<NameValuesHolder>) mPendingAnimations.clone(); 737 mPendingAnimations.clear(); 738 int propertyMask = 0; 739 int propertyCount = nameValueList.size(); 740 for (int i = 0; i < propertyCount; ++i) { 741 NameValuesHolder nameValuesHolder = nameValueList.get(i); 742 propertyMask |= nameValuesHolder.mNameConstant; 743 } 744 mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList)); 745 if (mPendingSetupAction != null) { 746 mAnimatorSetupMap.put(animator, mPendingSetupAction); 747 mPendingSetupAction = null; 748 } 749 if (mPendingCleanupAction != null) { 750 mAnimatorCleanupMap.put(animator, mPendingCleanupAction); 751 mPendingCleanupAction = null; 752 } 753 if (mPendingOnStartAction != null) { 754 mAnimatorOnStartMap.put(animator, mPendingOnStartAction); 755 mPendingOnStartAction = null; 756 } 757 if (mPendingOnEndAction != null) { 758 mAnimatorOnEndMap.put(animator, mPendingOnEndAction); 759 mPendingOnEndAction = null; 760 } 761 animator.addUpdateListener(mAnimatorEventListener); 762 animator.addListener(mAnimatorEventListener); 763 if (mStartDelaySet) { 764 animator.setStartDelay(mStartDelay); 765 } 766 if (mDurationSet) { 767 animator.setDuration(mDuration); 768 } 769 if (mInterpolatorSet) { 770 animator.setInterpolator(mInterpolator); 771 } 772 animator.start(); 773 } 774 775 /** 776 * Utility function, called by the various x(), y(), etc. methods. This stores the 777 * constant name for the property along with the from/delta values that will be used to 778 * calculate and set the property during the animation. This structure is added to the 779 * pending animations, awaiting the eventual start() of the underlying animator. A 780 * Runnable is posted to start the animation, and any pending such Runnable is canceled 781 * (which enables us to end up starting just one animator for all of the properties 782 * specified at one time). 783 * 784 * @param constantName The specifier for the property being animated 785 * @param toValue The value to which the property will animate 786 */ 787 private void animateProperty(int constantName, float toValue) { 788 float fromValue = getValue(constantName); 789 float deltaValue = toValue - fromValue; 790 animatePropertyBy(constantName, fromValue, deltaValue); 791 } 792 793 /** 794 * Utility function, called by the various xBy(), yBy(), etc. methods. This method is 795 * just like animateProperty(), except the value is an offset from the property's 796 * current value, instead of an absolute "to" value. 797 * 798 * @param constantName The specifier for the property being animated 799 * @param byValue The amount by which the property will change 800 */ 801 private void animatePropertyBy(int constantName, float byValue) { 802 float fromValue = getValue(constantName); 803 animatePropertyBy(constantName, fromValue, byValue); 804 } 805 806 /** 807 * Utility function, called by animateProperty() and animatePropertyBy(), which handles the 808 * details of adding a pending animation and posting the request to start the animation. 809 * 810 * @param constantName The specifier for the property being animated 811 * @param startValue The starting value of the property 812 * @param byValue The amount by which the property will change 813 */ 814 private void animatePropertyBy(int constantName, float startValue, float byValue) { 815 // First, cancel any existing animations on this property 816 if (mAnimatorMap.size() > 0) { 817 Animator animatorToCancel = null; 818 Set<Animator> animatorSet = mAnimatorMap.keySet(); 819 for (Animator runningAnim : animatorSet) { 820 PropertyBundle bundle = mAnimatorMap.get(runningAnim); 821 if (bundle.cancel(constantName)) { 822 // property was canceled - cancel the animation if it's now empty 823 // Note that it's safe to break out here because every new animation 824 // on a property will cancel a previous animation on that property, so 825 // there can only ever be one such animation running. 826 if (bundle.mPropertyMask == NONE) { 827 // the animation is no longer changing anything - cancel it 828 animatorToCancel = runningAnim; 829 break; 830 } 831 } 832 } 833 if (animatorToCancel != null) { 834 animatorToCancel.cancel(); 835 } 836 } 837 838 NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue); 839 mPendingAnimations.add(nameValuePair); 840 mView.removeCallbacks(mAnimationStarter); 841 mView.postOnAnimation(mAnimationStarter); 842 } 843 844 /** 845 * This method handles setting the property values directly in the View object's fields. 846 * propertyConstant tells it which property should be set, value is the value to set 847 * the property to. 848 * 849 * @param propertyConstant The property to be set 850 * @param value The value to set the property to 851 */ 852 private void setValue(int propertyConstant, float value) { 853 final View.TransformationInfo info = mView.mTransformationInfo; 854 final DisplayList displayList = mView.mDisplayList; 855 switch (propertyConstant) { 856 case TRANSLATION_X: 857 info.mTranslationX = value; 858 if (displayList != null) displayList.setTranslationX(value); 859 break; 860 case TRANSLATION_Y: 861 info.mTranslationY = value; 862 if (displayList != null) displayList.setTranslationY(value); 863 break; 864 case ROTATION: 865 info.mRotation = value; 866 if (displayList != null) displayList.setRotation(value); 867 break; 868 case ROTATION_X: 869 info.mRotationX = value; 870 if (displayList != null) displayList.setRotationX(value); 871 break; 872 case ROTATION_Y: 873 info.mRotationY = value; 874 if (displayList != null) displayList.setRotationY(value); 875 break; 876 case SCALE_X: 877 info.mScaleX = value; 878 if (displayList != null) displayList.setScaleX(value); 879 break; 880 case SCALE_Y: 881 info.mScaleY = value; 882 if (displayList != null) displayList.setScaleY(value); 883 break; 884 case X: 885 info.mTranslationX = value - mView.mLeft; 886 if (displayList != null) displayList.setTranslationX(value - mView.mLeft); 887 break; 888 case Y: 889 info.mTranslationY = value - mView.mTop; 890 if (displayList != null) displayList.setTranslationY(value - mView.mTop); 891 break; 892 case ALPHA: 893 info.mAlpha = value; 894 if (displayList != null) displayList.setAlpha(value); 895 break; 896 } 897 } 898 899 /** 900 * This method gets the value of the named property from the View object. 901 * 902 * @param propertyConstant The property whose value should be returned 903 * @return float The value of the named property 904 */ 905 private float getValue(int propertyConstant) { 906 final View.TransformationInfo info = mView.mTransformationInfo; 907 switch (propertyConstant) { 908 case TRANSLATION_X: 909 return info.mTranslationX; 910 case TRANSLATION_Y: 911 return info.mTranslationY; 912 case ROTATION: 913 return info.mRotation; 914 case ROTATION_X: 915 return info.mRotationX; 916 case ROTATION_Y: 917 return info.mRotationY; 918 case SCALE_X: 919 return info.mScaleX; 920 case SCALE_Y: 921 return info.mScaleY; 922 case X: 923 return mView.mLeft + info.mTranslationX; 924 case Y: 925 return mView.mTop + info.mTranslationY; 926 case ALPHA: 927 return info.mAlpha; 928 } 929 return 0; 930 } 931 932 /** 933 * Utility class that handles the various Animator events. The only ones we care 934 * about are the end event (which we use to clean up the animator map when an animator 935 * finishes) and the update event (which we use to calculate the current value of each 936 * property and then set it on the view object). 937 */ 938 private class AnimatorEventListener 939 implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener { 940 @Override 941 public void onAnimationStart(Animator animation) { 942 if (mAnimatorSetupMap != null) { 943 Runnable r = mAnimatorSetupMap.get(animation); 944 if (r != null) { 945 r.run(); 946 } 947 mAnimatorSetupMap.remove(animation); 948 } 949 if (mAnimatorOnStartMap != null) { 950 Runnable r = mAnimatorOnStartMap.get(animation); 951 if (r != null) { 952 r.run(); 953 } 954 mAnimatorOnStartMap.remove(animation); 955 } 956 if (mListener != null) { 957 mListener.onAnimationStart(animation); 958 } 959 } 960 961 @Override 962 public void onAnimationCancel(Animator animation) { 963 if (mListener != null) { 964 mListener.onAnimationCancel(animation); 965 } 966 if (mAnimatorOnEndMap != null) { 967 mAnimatorOnEndMap.remove(animation); 968 } 969 } 970 971 @Override 972 public void onAnimationRepeat(Animator animation) { 973 if (mListener != null) { 974 mListener.onAnimationRepeat(animation); 975 } 976 } 977 978 @Override 979 public void onAnimationEnd(Animator animation) { 980 mView.setHasTransientState(false); 981 if (mListener != null) { 982 mListener.onAnimationEnd(animation); 983 } 984 if (mAnimatorOnEndMap != null) { 985 Runnable r = mAnimatorOnEndMap.get(animation); 986 if (r != null) { 987 r.run(); 988 } 989 mAnimatorOnEndMap.remove(animation); 990 } 991 if (mAnimatorCleanupMap != null) { 992 Runnable r = mAnimatorCleanupMap.get(animation); 993 if (r != null) { 994 r.run(); 995 } 996 mAnimatorCleanupMap.remove(animation); 997 } 998 mAnimatorMap.remove(animation); 999 } 1000 1001 /** 1002 * Calculate the current value for each property and set it on the view. Invalidate 1003 * the view object appropriately, depending on which properties are being animated. 1004 * 1005 * @param animation The animator associated with the properties that need to be 1006 * set. This animator holds the animation fraction which we will use to calculate 1007 * the current value of each property. 1008 */ 1009 @Override 1010 public void onAnimationUpdate(ValueAnimator animation) { 1011 PropertyBundle propertyBundle = mAnimatorMap.get(animation); 1012 if (propertyBundle == null) { 1013 // Shouldn't happen, but just to play it safe 1014 return; 1015 } 1016 boolean useDisplayListProperties = mView.mDisplayList != null; 1017 1018 // alpha requires slightly different treatment than the other (transform) properties. 1019 // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation 1020 // logic is dependent on how the view handles an internal call to onSetAlpha(). 1021 // We track what kinds of properties are set, and how alpha is handled when it is 1022 // set, and perform the invalidation steps appropriately. 1023 boolean alphaHandled = false; 1024 if (!useDisplayListProperties) { 1025 mView.invalidateParentCaches(); 1026 } 1027 float fraction = animation.getAnimatedFraction(); 1028 int propertyMask = propertyBundle.mPropertyMask; 1029 if ((propertyMask & TRANSFORM_MASK) != 0) { 1030 mView.invalidateViewProperty(false, false); 1031 } 1032 ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder; 1033 if (valueList != null) { 1034 int count = valueList.size(); 1035 for (int i = 0; i < count; ++i) { 1036 NameValuesHolder values = valueList.get(i); 1037 float value = values.mFromValue + fraction * values.mDeltaValue; 1038 if (values.mNameConstant == ALPHA) { 1039 alphaHandled = mView.setAlphaNoInvalidation(value); 1040 } else { 1041 setValue(values.mNameConstant, value); 1042 } 1043 } 1044 } 1045 if ((propertyMask & TRANSFORM_MASK) != 0) { 1046 mView.mTransformationInfo.mMatrixDirty = true; 1047 if (!useDisplayListProperties) { 1048 mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation 1049 } 1050 } 1051 // invalidate(false) in all cases except if alphaHandled gets set to true 1052 // via the call to setAlphaNoInvalidation(), above 1053 if (alphaHandled) { 1054 mView.invalidate(true); 1055 } else { 1056 mView.invalidateViewProperty(false, false); 1057 } 1058 } 1059 } 1060 } 1061