1 /* 2 * Copyright (C) 2010 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.animation; 18 19 import android.view.View; 20 import android.view.ViewGroup; 21 import android.view.ViewParent; 22 import android.view.ViewTreeObserver; 23 import android.view.animation.AccelerateDecelerateInterpolator; 24 import android.view.animation.DecelerateInterpolator; 25 26 import java.util.ArrayList; 27 import java.util.Collection; 28 import java.util.HashMap; 29 import java.util.LinkedHashMap; 30 import java.util.List; 31 32 /** 33 * This class enables automatic animations on layout changes in ViewGroup objects. To enable 34 * transitions for a layout container, create a LayoutTransition object and set it on any 35 * ViewGroup by calling {@link ViewGroup#setLayoutTransition(LayoutTransition)}. This will cause 36 * default animations to run whenever items are added to or removed from that container. To specify 37 * custom animations, use the {@link LayoutTransition#setAnimator(int, Animator) 38 * setAnimator()} method. 39 * 40 * <p>One of the core concepts of these transition animations is that there are two types of 41 * changes that cause the transition and four different animations that run because of 42 * those changes. The changes that trigger the transition are items being added to a container 43 * (referred to as an "appearing" transition) or removed from a container (also known as 44 * "disappearing"). Setting the visibility of views (between GONE and VISIBLE) will trigger 45 * the same add/remove logic. The animations that run due to those events are one that animates 46 * items being added, one that animates items being removed, and two that animate the other 47 * items in the container that change due to the add/remove occurrence. Users of 48 * the transition may want different animations for the changing items depending on whether 49 * they are changing due to an appearing or disappearing event, so there is one animation for 50 * each of these variations of the changing event. Most of the API of this class is concerned 51 * with setting up the basic properties of the animations used in these four situations, 52 * or with setting up custom animations for any or all of the four.</p> 53 * 54 * <p>By default, the DISAPPEARING animation begins immediately, as does the CHANGE_APPEARING 55 * animation. The other animations begin after a delay that is set to the default duration 56 * of the animations. This behavior facilitates a sequence of animations in transitions as 57 * follows: when an item is being added to a layout, the other children of that container will 58 * move first (thus creating space for the new item), then the appearing animation will run to 59 * animate the item being added. Conversely, when an item is removed from a container, the 60 * animation to remove it will run first, then the animations of the other children in the 61 * layout will run (closing the gap created in the layout when the item was removed). If this 62 * default choreography behavior is not desired, the {@link #setDuration(int, long)} and 63 * {@link #setStartDelay(int, long)} of any or all of the animations can be changed as 64 * appropriate.</p> 65 * 66 * <p>The animations specified for the transition, both the defaults and any custom animations 67 * set on the transition object, are templates only. That is, these animations exist to hold the 68 * basic animation properties, such as the duration, start delay, and properties being animated. 69 * But the actual target object, as well as the start and end values for those properties, are 70 * set automatically in the process of setting up the transition each time it runs. Each of the 71 * animations is cloned from the original copy and the clone is then populated with the dynamic 72 * values of the target being animated (such as one of the items in a layout container that is 73 * moving as a result of the layout event) as well as the values that are changing (such as the 74 * position and size of that object). The actual values that are pushed to each animation 75 * depends on what properties are specified for the animation. For example, the default 76 * CHANGE_APPEARING animation animates the <code>left</code>, <code>top</code>, <code>right</code>, 77 * <code>bottom</code>, <code>scrollX</code>, and <code>scrollY</code> properties. 78 * Values for these properties are updated with the pre- and post-layout 79 * values when the transition begins. Custom animations will be similarly populated with 80 * the target and values being animated, assuming they use ObjectAnimator objects with 81 * property names that are known on the target object.</p> 82 * 83 * <p>This class, and the associated XML flag for containers, animateLayoutChanges="true", 84 * provides a simple utility meant for automating changes in straightforward situations. 85 * Using LayoutTransition at multiple levels of a nested view hierarchy may not work due to the 86 * interrelationship of the various levels of layout. Also, a container that is being scrolled 87 * at the same time as items are being added or removed is probably not a good candidate for 88 * this utility, because the before/after locations calculated by LayoutTransition 89 * may not match the actual locations when the animations finish due to the container 90 * being scrolled as the animations are running. You can work around that 91 * particular issue by disabling the 'changing' animations by setting the CHANGE_APPEARING 92 * and CHANGE_DISAPPEARING animations to null, and setting the startDelay of the 93 * other animations appropriately.</p> 94 */ 95 public class LayoutTransition { 96 97 /** 98 * A flag indicating the animation that runs on those items that are changing 99 * due to a new item appearing in the container. 100 */ 101 public static final int CHANGE_APPEARING = 0; 102 103 /** 104 * A flag indicating the animation that runs on those items that are changing 105 * due to an item disappearing from the container. 106 */ 107 public static final int CHANGE_DISAPPEARING = 1; 108 109 /** 110 * A flag indicating the animation that runs on those items that are appearing 111 * in the container. 112 */ 113 public static final int APPEARING = 2; 114 115 /** 116 * A flag indicating the animation that runs on those items that are disappearing 117 * from the container. 118 */ 119 public static final int DISAPPEARING = 3; 120 121 /** 122 * A flag indicating the animation that runs on those items that are changing 123 * due to a layout change not caused by items being added to or removed 124 * from the container. This transition type is not enabled by default; it can be 125 * enabled via {@link #enableTransitionType(int)}. 126 */ 127 public static final int CHANGING = 4; 128 129 /** 130 * Private bit fields used to set the collection of enabled transition types for 131 * mTransitionTypes. 132 */ 133 private static final int FLAG_APPEARING = 0x01; 134 private static final int FLAG_DISAPPEARING = 0x02; 135 private static final int FLAG_CHANGE_APPEARING = 0x04; 136 private static final int FLAG_CHANGE_DISAPPEARING = 0x08; 137 private static final int FLAG_CHANGING = 0x10; 138 139 /** 140 * These variables hold the animations that are currently used to run the transition effects. 141 * These animations are set to defaults, but can be changed to custom animations by 142 * calls to setAnimator(). 143 */ 144 private Animator mDisappearingAnim = null; 145 private Animator mAppearingAnim = null; 146 private Animator mChangingAppearingAnim = null; 147 private Animator mChangingDisappearingAnim = null; 148 private Animator mChangingAnim = null; 149 150 /** 151 * These are the default animations, defined in the constructor, that will be used 152 * unless the user specifies custom animations. 153 */ 154 private static ObjectAnimator defaultChange; 155 private static ObjectAnimator defaultChangeIn; 156 private static ObjectAnimator defaultChangeOut; 157 private static ObjectAnimator defaultFadeIn; 158 private static ObjectAnimator defaultFadeOut; 159 160 /** 161 * The default duration used by all animations. 162 */ 163 private static long DEFAULT_DURATION = 300; 164 165 /** 166 * The durations of the different animations 167 */ 168 private long mChangingAppearingDuration = DEFAULT_DURATION; 169 private long mChangingDisappearingDuration = DEFAULT_DURATION; 170 private long mChangingDuration = DEFAULT_DURATION; 171 private long mAppearingDuration = DEFAULT_DURATION; 172 private long mDisappearingDuration = DEFAULT_DURATION; 173 174 /** 175 * The start delays of the different animations. Note that the default behavior of 176 * the appearing item is the default duration, since it should wait for the items to move 177 * before fading it. Same for the changing animation when disappearing; it waits for the item 178 * to fade out before moving the other items. 179 */ 180 private long mAppearingDelay = DEFAULT_DURATION; 181 private long mDisappearingDelay = 0; 182 private long mChangingAppearingDelay = 0; 183 private long mChangingDisappearingDelay = DEFAULT_DURATION; 184 private long mChangingDelay = 0; 185 186 /** 187 * The inter-animation delays used on the changing animations 188 */ 189 private long mChangingAppearingStagger = 0; 190 private long mChangingDisappearingStagger = 0; 191 private long mChangingStagger = 0; 192 193 /** 194 * Static interpolators - these are stateless and can be shared across the instances 195 */ 196 private static TimeInterpolator ACCEL_DECEL_INTERPOLATOR = 197 new AccelerateDecelerateInterpolator(); 198 private static TimeInterpolator DECEL_INTERPOLATOR = new DecelerateInterpolator(); 199 private static TimeInterpolator sAppearingInterpolator = ACCEL_DECEL_INTERPOLATOR; 200 private static TimeInterpolator sDisappearingInterpolator = ACCEL_DECEL_INTERPOLATOR; 201 private static TimeInterpolator sChangingAppearingInterpolator = DECEL_INTERPOLATOR; 202 private static TimeInterpolator sChangingDisappearingInterpolator = DECEL_INTERPOLATOR; 203 private static TimeInterpolator sChangingInterpolator = DECEL_INTERPOLATOR; 204 205 /** 206 * The default interpolators used for the animations 207 */ 208 private TimeInterpolator mAppearingInterpolator = sAppearingInterpolator; 209 private TimeInterpolator mDisappearingInterpolator = sDisappearingInterpolator; 210 private TimeInterpolator mChangingAppearingInterpolator = sChangingAppearingInterpolator; 211 private TimeInterpolator mChangingDisappearingInterpolator = sChangingDisappearingInterpolator; 212 private TimeInterpolator mChangingInterpolator = sChangingInterpolator; 213 214 /** 215 * These hashmaps are used to store the animations that are currently running as part of 216 * the transition. The reason for this is that a further layout event should cause 217 * existing animations to stop where they are prior to starting new animations. So 218 * we cache all of the current animations in this map for possible cancellation on 219 * another layout event. LinkedHashMaps are used to preserve the order in which animations 220 * are inserted, so that we process events (such as setting up start values) in the same order. 221 */ 222 private final HashMap<View, Animator> pendingAnimations = 223 new HashMap<View, Animator>(); 224 private final LinkedHashMap<View, Animator> currentChangingAnimations = 225 new LinkedHashMap<View, Animator>(); 226 private final LinkedHashMap<View, Animator> currentAppearingAnimations = 227 new LinkedHashMap<View, Animator>(); 228 private final LinkedHashMap<View, Animator> currentDisappearingAnimations = 229 new LinkedHashMap<View, Animator>(); 230 231 /** 232 * This hashmap is used to track the listeners that have been added to the children of 233 * a container. When a layout change occurs, an animation is created for each View, so that 234 * the pre-layout values can be cached in that animation. Then a listener is added to the 235 * view to see whether the layout changes the bounds of that view. If so, the animation 236 * is set with the final values and then run. If not, the animation is not started. When 237 * the process of setting up and running all appropriate animations is done, we need to 238 * remove these listeners and clear out the map. 239 */ 240 private final HashMap<View, View.OnLayoutChangeListener> layoutChangeListenerMap = 241 new HashMap<View, View.OnLayoutChangeListener>(); 242 243 /** 244 * Used to track the current delay being assigned to successive animations as they are 245 * started. This value is incremented for each new animation, then zeroed before the next 246 * transition begins. 247 */ 248 private long staggerDelay; 249 250 /** 251 * These are the types of transition animations that the LayoutTransition is reacting 252 * to. By default, appearing/disappearing and the change animations related to them are 253 * enabled (not CHANGING). 254 */ 255 private int mTransitionTypes = FLAG_CHANGE_APPEARING | FLAG_CHANGE_DISAPPEARING | 256 FLAG_APPEARING | FLAG_DISAPPEARING; 257 /** 258 * The set of listeners that should be notified when APPEARING/DISAPPEARING transitions 259 * start and end. 260 */ 261 private ArrayList<TransitionListener> mListeners; 262 263 /** 264 * Controls whether changing animations automatically animate the parent hierarchy as well. 265 * This behavior prevents artifacts when wrap_content layouts snap to the end state as the 266 * transition begins, causing visual glitches and clipping. 267 * Default value is true. 268 */ 269 private boolean mAnimateParentHierarchy = true; 270 271 272 /** 273 * Constructs a LayoutTransition object. By default, the object will listen to layout 274 * events on any ViewGroup that it is set on and will run default animations for each 275 * type of layout event. 276 */ 277 public LayoutTransition() { 278 if (defaultChangeIn == null) { 279 // "left" is just a placeholder; we'll put real properties/values in when needed 280 PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1); 281 PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1); 282 PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1); 283 PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1); 284 PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1); 285 PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1); 286 defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null, 287 pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY); 288 defaultChangeIn.setDuration(DEFAULT_DURATION); 289 defaultChangeIn.setStartDelay(mChangingAppearingDelay); 290 defaultChangeIn.setInterpolator(mChangingAppearingInterpolator); 291 defaultChangeOut = defaultChangeIn.clone(); 292 defaultChangeOut.setStartDelay(mChangingDisappearingDelay); 293 defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator); 294 defaultChange = defaultChangeIn.clone(); 295 defaultChange.setStartDelay(mChangingDelay); 296 defaultChange.setInterpolator(mChangingInterpolator); 297 298 defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f); 299 defaultFadeIn.setDuration(DEFAULT_DURATION); 300 defaultFadeIn.setStartDelay(mAppearingDelay); 301 defaultFadeIn.setInterpolator(mAppearingInterpolator); 302 defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f); 303 defaultFadeOut.setDuration(DEFAULT_DURATION); 304 defaultFadeOut.setStartDelay(mDisappearingDelay); 305 defaultFadeOut.setInterpolator(mDisappearingInterpolator); 306 } 307 mChangingAppearingAnim = defaultChangeIn; 308 mChangingDisappearingAnim = defaultChangeOut; 309 mChangingAnim = defaultChange; 310 mAppearingAnim = defaultFadeIn; 311 mDisappearingAnim = defaultFadeOut; 312 } 313 314 /** 315 * Sets the duration to be used by all animations of this transition object. If you want to 316 * set the duration of just one of the animations in particular, use the 317 * {@link #setDuration(int, long)} method. 318 * 319 * @param duration The length of time, in milliseconds, that the transition animations 320 * should last. 321 */ 322 public void setDuration(long duration) { 323 mChangingAppearingDuration = duration; 324 mChangingDisappearingDuration = duration; 325 mChangingDuration = duration; 326 mAppearingDuration = duration; 327 mDisappearingDuration = duration; 328 } 329 330 /** 331 * Enables the specified transitionType for this LayoutTransition object. 332 * By default, a LayoutTransition listens for changes in children being 333 * added/remove/hidden/shown in the container, and runs the animations associated with 334 * those events. That is, all transition types besides {@link #CHANGING} are enabled by default. 335 * You can also enable {@link #CHANGING} animations by calling this method with the 336 * {@link #CHANGING} transitionType. 337 * 338 * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 339 * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}. 340 */ 341 public void enableTransitionType(int transitionType) { 342 switch (transitionType) { 343 case APPEARING: 344 mTransitionTypes |= FLAG_APPEARING; 345 break; 346 case DISAPPEARING: 347 mTransitionTypes |= FLAG_DISAPPEARING; 348 break; 349 case CHANGE_APPEARING: 350 mTransitionTypes |= FLAG_CHANGE_APPEARING; 351 break; 352 case CHANGE_DISAPPEARING: 353 mTransitionTypes |= FLAG_CHANGE_DISAPPEARING; 354 break; 355 case CHANGING: 356 mTransitionTypes |= FLAG_CHANGING; 357 break; 358 } 359 } 360 361 /** 362 * Disables the specified transitionType for this LayoutTransition object. 363 * By default, all transition types except {@link #CHANGING} are enabled. 364 * 365 * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 366 * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}. 367 */ 368 public void disableTransitionType(int transitionType) { 369 switch (transitionType) { 370 case APPEARING: 371 mTransitionTypes &= ~FLAG_APPEARING; 372 break; 373 case DISAPPEARING: 374 mTransitionTypes &= ~FLAG_DISAPPEARING; 375 break; 376 case CHANGE_APPEARING: 377 mTransitionTypes &= ~FLAG_CHANGE_APPEARING; 378 break; 379 case CHANGE_DISAPPEARING: 380 mTransitionTypes &= ~FLAG_CHANGE_DISAPPEARING; 381 break; 382 case CHANGING: 383 mTransitionTypes &= ~FLAG_CHANGING; 384 break; 385 } 386 } 387 388 /** 389 * Returns whether the specified transitionType is enabled for this LayoutTransition object. 390 * By default, all transition types except {@link #CHANGING} are enabled. 391 * 392 * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 393 * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}. 394 * @return true if the specified transitionType is currently enabled, false otherwise. 395 */ 396 public boolean isTransitionTypeEnabled(int transitionType) { 397 switch (transitionType) { 398 case APPEARING: 399 return (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING; 400 case DISAPPEARING: 401 return (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING; 402 case CHANGE_APPEARING: 403 return (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING; 404 case CHANGE_DISAPPEARING: 405 return (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING; 406 case CHANGING: 407 return (mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING; 408 } 409 return false; 410 } 411 412 /** 413 * Sets the start delay on one of the animation objects used by this transition. The 414 * <code>transitionType</code> parameter determines the animation whose start delay 415 * is being set. 416 * 417 * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 418 * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines 419 * the animation whose start delay is being set. 420 * @param delay The length of time, in milliseconds, to delay before starting the animation. 421 * @see Animator#setStartDelay(long) 422 */ 423 public void setStartDelay(int transitionType, long delay) { 424 switch (transitionType) { 425 case CHANGE_APPEARING: 426 mChangingAppearingDelay = delay; 427 break; 428 case CHANGE_DISAPPEARING: 429 mChangingDisappearingDelay = delay; 430 break; 431 case CHANGING: 432 mChangingDelay = delay; 433 break; 434 case APPEARING: 435 mAppearingDelay = delay; 436 break; 437 case DISAPPEARING: 438 mDisappearingDelay = delay; 439 break; 440 } 441 } 442 443 /** 444 * Gets the start delay on one of the animation objects used by this transition. The 445 * <code>transitionType</code> parameter determines the animation whose start delay 446 * is returned. 447 * 448 * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 449 * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines 450 * the animation whose start delay is returned. 451 * @return long The start delay of the specified animation. 452 * @see Animator#getStartDelay() 453 */ 454 public long getStartDelay(int transitionType) { 455 switch (transitionType) { 456 case CHANGE_APPEARING: 457 return mChangingAppearingDelay; 458 case CHANGE_DISAPPEARING: 459 return mChangingDisappearingDelay; 460 case CHANGING: 461 return mChangingDelay; 462 case APPEARING: 463 return mAppearingDelay; 464 case DISAPPEARING: 465 return mDisappearingDelay; 466 } 467 // shouldn't reach here 468 return 0; 469 } 470 471 /** 472 * Sets the duration on one of the animation objects used by this transition. The 473 * <code>transitionType</code> parameter determines the animation whose duration 474 * is being set. 475 * 476 * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 477 * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines 478 * the animation whose duration is being set. 479 * @param duration The length of time, in milliseconds, that the specified animation should run. 480 * @see Animator#setDuration(long) 481 */ 482 public void setDuration(int transitionType, long duration) { 483 switch (transitionType) { 484 case CHANGE_APPEARING: 485 mChangingAppearingDuration = duration; 486 break; 487 case CHANGE_DISAPPEARING: 488 mChangingDisappearingDuration = duration; 489 break; 490 case CHANGING: 491 mChangingDuration = duration; 492 break; 493 case APPEARING: 494 mAppearingDuration = duration; 495 break; 496 case DISAPPEARING: 497 mDisappearingDuration = duration; 498 break; 499 } 500 } 501 502 /** 503 * Gets the duration on one of the animation objects used by this transition. The 504 * <code>transitionType</code> parameter determines the animation whose duration 505 * is returned. 506 * 507 * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 508 * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines 509 * the animation whose duration is returned. 510 * @return long The duration of the specified animation. 511 * @see Animator#getDuration() 512 */ 513 public long getDuration(int transitionType) { 514 switch (transitionType) { 515 case CHANGE_APPEARING: 516 return mChangingAppearingDuration; 517 case CHANGE_DISAPPEARING: 518 return mChangingDisappearingDuration; 519 case CHANGING: 520 return mChangingDuration; 521 case APPEARING: 522 return mAppearingDuration; 523 case DISAPPEARING: 524 return mDisappearingDuration; 525 } 526 // shouldn't reach here 527 return 0; 528 } 529 530 /** 531 * Sets the length of time to delay between starting each animation during one of the 532 * change animations. 533 * 534 * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or 535 * {@link #CHANGING}. 536 * @param duration The length of time, in milliseconds, to delay before launching the next 537 * animation in the sequence. 538 */ 539 public void setStagger(int transitionType, long duration) { 540 switch (transitionType) { 541 case CHANGE_APPEARING: 542 mChangingAppearingStagger = duration; 543 break; 544 case CHANGE_DISAPPEARING: 545 mChangingDisappearingStagger = duration; 546 break; 547 case CHANGING: 548 mChangingStagger = duration; 549 break; 550 // noop other cases 551 } 552 } 553 554 /** 555 * Gets the length of time to delay between starting each animation during one of the 556 * change animations. 557 * 558 * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or 559 * {@link #CHANGING}. 560 * @return long The length of time, in milliseconds, to delay before launching the next 561 * animation in the sequence. 562 */ 563 public long getStagger(int transitionType) { 564 switch (transitionType) { 565 case CHANGE_APPEARING: 566 return mChangingAppearingStagger; 567 case CHANGE_DISAPPEARING: 568 return mChangingDisappearingStagger; 569 case CHANGING: 570 return mChangingStagger; 571 } 572 // shouldn't reach here 573 return 0; 574 } 575 576 /** 577 * Sets the interpolator on one of the animation objects used by this transition. The 578 * <code>transitionType</code> parameter determines the animation whose interpolator 579 * is being set. 580 * 581 * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 582 * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines 583 * the animation whose interpolator is being set. 584 * @param interpolator The interpolator that the specified animation should use. 585 * @see Animator#setInterpolator(TimeInterpolator) 586 */ 587 public void setInterpolator(int transitionType, TimeInterpolator interpolator) { 588 switch (transitionType) { 589 case CHANGE_APPEARING: 590 mChangingAppearingInterpolator = interpolator; 591 break; 592 case CHANGE_DISAPPEARING: 593 mChangingDisappearingInterpolator = interpolator; 594 break; 595 case CHANGING: 596 mChangingInterpolator = interpolator; 597 break; 598 case APPEARING: 599 mAppearingInterpolator = interpolator; 600 break; 601 case DISAPPEARING: 602 mDisappearingInterpolator = interpolator; 603 break; 604 } 605 } 606 607 /** 608 * Gets the interpolator on one of the animation objects used by this transition. The 609 * <code>transitionType</code> parameter determines the animation whose interpolator 610 * is returned. 611 * 612 * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 613 * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines 614 * the animation whose interpolator is being returned. 615 * @return TimeInterpolator The interpolator that the specified animation uses. 616 * @see Animator#setInterpolator(TimeInterpolator) 617 */ 618 public TimeInterpolator getInterpolator(int transitionType) { 619 switch (transitionType) { 620 case CHANGE_APPEARING: 621 return mChangingAppearingInterpolator; 622 case CHANGE_DISAPPEARING: 623 return mChangingDisappearingInterpolator; 624 case CHANGING: 625 return mChangingInterpolator; 626 case APPEARING: 627 return mAppearingInterpolator; 628 case DISAPPEARING: 629 return mDisappearingInterpolator; 630 } 631 // shouldn't reach here 632 return null; 633 } 634 635 /** 636 * Sets the animation used during one of the transition types that may run. Any 637 * Animator object can be used, but to be most useful in the context of layout 638 * transitions, the animation should either be a ObjectAnimator or a AnimatorSet 639 * of animations including PropertyAnimators. Also, these ObjectAnimator objects 640 * should be able to get and set values on their target objects automatically. For 641 * example, a ObjectAnimator that animates the property "left" is able to set and get the 642 * <code>left</code> property from the View objects being animated by the layout 643 * transition. The transition works by setting target objects and properties 644 * dynamically, according to the pre- and post-layoout values of those objects, so 645 * having animations that can handle those properties appropriately will work best 646 * for custom animation. The dynamic setting of values is only the case for the 647 * CHANGE animations; the APPEARING and DISAPPEARING animations are simply run with 648 * the values they have. 649 * 650 * <p>It is also worth noting that any and all animations (and their underlying 651 * PropertyValuesHolder objects) will have their start and end values set according 652 * to the pre- and post-layout values. So, for example, a custom animation on "alpha" 653 * as the CHANGE_APPEARING animation will inherit the real value of alpha on the target 654 * object (presumably 1) as its starting and ending value when the animation begins. 655 * Animations which need to use values at the beginning and end that may not match the 656 * values queried when the transition begins may need to use a different mechanism 657 * than a standard ObjectAnimator object.</p> 658 * 659 * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 660 * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the 661 * animation whose animator is being set. 662 * @param animator The animation being assigned. A value of <code>null</code> means that no 663 * animation will be run for the specified transitionType. 664 */ 665 public void setAnimator(int transitionType, Animator animator) { 666 switch (transitionType) { 667 case CHANGE_APPEARING: 668 mChangingAppearingAnim = animator; 669 break; 670 case CHANGE_DISAPPEARING: 671 mChangingDisappearingAnim = animator; 672 break; 673 case CHANGING: 674 mChangingAnim = animator; 675 break; 676 case APPEARING: 677 mAppearingAnim = animator; 678 break; 679 case DISAPPEARING: 680 mDisappearingAnim = animator; 681 break; 682 } 683 } 684 685 /** 686 * Gets the animation used during one of the transition types that may run. 687 * 688 * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 689 * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines 690 * the animation whose animator is being returned. 691 * @return Animator The animation being used for the given transition type. 692 * @see #setAnimator(int, Animator) 693 */ 694 public Animator getAnimator(int transitionType) { 695 switch (transitionType) { 696 case CHANGE_APPEARING: 697 return mChangingAppearingAnim; 698 case CHANGE_DISAPPEARING: 699 return mChangingDisappearingAnim; 700 case CHANGING: 701 return mChangingAnim; 702 case APPEARING: 703 return mAppearingAnim; 704 case DISAPPEARING: 705 return mDisappearingAnim; 706 } 707 // shouldn't reach here 708 return null; 709 } 710 711 /** 712 * This function sets up animations on all of the views that change during layout. 713 * For every child in the parent, we create a change animation of the appropriate 714 * type (appearing, disappearing, or changing) and ask it to populate its start values from its 715 * target view. We add layout listeners to all child views and listen for changes. For 716 * those views that change, we populate the end values for those animations and start them. 717 * Animations are not run on unchanging views. 718 * 719 * @param parent The container which is undergoing a change. 720 * @param newView The view being added to or removed from the parent. May be null if the 721 * changeReason is CHANGING. 722 * @param changeReason A value of APPEARING, DISAPPEARING, or CHANGING, indicating whether the 723 * transition is occurring because an item is being added to or removed from the parent, or 724 * if it is running in response to a layout operation (that is, if the value is CHANGING). 725 */ 726 private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) { 727 728 Animator baseAnimator = null; 729 Animator parentAnimator = null; 730 final long duration; 731 switch (changeReason) { 732 case APPEARING: 733 baseAnimator = mChangingAppearingAnim; 734 duration = mChangingAppearingDuration; 735 parentAnimator = defaultChangeIn; 736 break; 737 case DISAPPEARING: 738 baseAnimator = mChangingDisappearingAnim; 739 duration = mChangingDisappearingDuration; 740 parentAnimator = defaultChangeOut; 741 break; 742 case CHANGING: 743 baseAnimator = mChangingAnim; 744 duration = mChangingDuration; 745 parentAnimator = defaultChange; 746 break; 747 default: 748 // Shouldn't reach here 749 duration = 0; 750 break; 751 } 752 // If the animation is null, there's nothing to do 753 if (baseAnimator == null) { 754 return; 755 } 756 757 // reset the inter-animation delay, in case we use it later 758 staggerDelay = 0; 759 760 final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup 761 if (!observer.isAlive()) { 762 // If the observer's not in a good state, skip the transition 763 return; 764 } 765 int numChildren = parent.getChildCount(); 766 767 for (int i = 0; i < numChildren; ++i) { 768 final View child = parent.getChildAt(i); 769 770 // only animate the views not being added or removed 771 if (child != newView) { 772 setupChangeAnimation(parent, changeReason, baseAnimator, duration, child); 773 } 774 } 775 if (mAnimateParentHierarchy) { 776 ViewGroup tempParent = parent; 777 while (tempParent != null) { 778 ViewParent parentParent = tempParent.getParent(); 779 if (parentParent instanceof ViewGroup) { 780 setupChangeAnimation((ViewGroup)parentParent, changeReason, parentAnimator, 781 duration, tempParent); 782 tempParent = (ViewGroup) parentParent; 783 } else { 784 tempParent = null; 785 } 786 787 } 788 } 789 790 // This is the cleanup step. When we get this rendering event, we know that all of 791 // the appropriate animations have been set up and run. Now we can clear out the 792 // layout listeners. 793 observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 794 public boolean onPreDraw() { 795 parent.getViewTreeObserver().removeOnPreDrawListener(this); 796 int count = layoutChangeListenerMap.size(); 797 if (count > 0) { 798 Collection<View> views = layoutChangeListenerMap.keySet(); 799 for (View view : views) { 800 View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view); 801 view.removeOnLayoutChangeListener(listener); 802 } 803 } 804 layoutChangeListenerMap.clear(); 805 return true; 806 } 807 }); 808 } 809 810 /** 811 * This flag controls whether CHANGE_APPEARING or CHANGE_DISAPPEARING animations will 812 * cause the default changing animation to be run on the parent hierarchy as well. This allows 813 * containers of transitioning views to also transition, which may be necessary in situations 814 * where the containers bounds change between the before/after states and may clip their 815 * children during the transition animations. For example, layouts with wrap_content will 816 * adjust their bounds according to the dimensions of their children. 817 * 818 * <p>The default changing transitions animate the bounds and scroll positions of the 819 * target views. These are the animations that will run on the parent hierarchy, not 820 * the custom animations that happen to be set on the transition. This allows custom 821 * behavior for the children of the transitioning container, but uses standard behavior 822 * of resizing/rescrolling on any changing parents. 823 * 824 * @param animateParentHierarchy A boolean value indicating whether the parents of 825 * transitioning views should also be animated during the transition. Default value is true. 826 */ 827 public void setAnimateParentHierarchy(boolean animateParentHierarchy) { 828 mAnimateParentHierarchy = animateParentHierarchy; 829 } 830 831 /** 832 * Utility function called by runChangingTransition for both the children and the parent 833 * hierarchy. 834 */ 835 private void setupChangeAnimation(final ViewGroup parent, final int changeReason, 836 Animator baseAnimator, final long duration, final View child) { 837 838 // If we already have a listener for this child, then we've already set up the 839 // changing animation we need. Multiple calls for a child may occur when several 840 // add/remove operations are run at once on a container; each one will trigger 841 // changes for the existing children in the container. 842 if (layoutChangeListenerMap.get(child) != null) { 843 return; 844 } 845 846 // Don't animate items up from size(0,0); this is likely because the objects 847 // were offscreen/invisible or otherwise measured to be infinitely small. We don't 848 // want to see them animate into their real size; just ignore animation requests 849 // on these views 850 if (child.getWidth() == 0 && child.getHeight() == 0) { 851 return; 852 } 853 854 // Make a copy of the appropriate animation 855 final Animator anim = baseAnimator.clone(); 856 857 // Set the target object for the animation 858 anim.setTarget(child); 859 860 // A ObjectAnimator (or AnimatorSet of them) can extract start values from 861 // its target object 862 anim.setupStartValues(); 863 864 // If there's an animation running on this view already, cancel it 865 Animator currentAnimation = pendingAnimations.get(child); 866 if (currentAnimation != null) { 867 currentAnimation.cancel(); 868 pendingAnimations.remove(child); 869 } 870 // Cache the animation in case we need to cancel it later 871 pendingAnimations.put(child, anim); 872 873 // For the animations which don't get started, we have to have a means of 874 // removing them from the cache, lest we leak them and their target objects. 875 // We run an animator for the default duration+100 (an arbitrary time, but one 876 // which should far surpass the delay between setting them up here and 877 // handling layout events which start them. 878 ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f). 879 setDuration(duration + 100); 880 pendingAnimRemover.addListener(new AnimatorListenerAdapter() { 881 @Override 882 public void onAnimationEnd(Animator animation) { 883 pendingAnimations.remove(child); 884 } 885 }); 886 pendingAnimRemover.start(); 887 888 // Add a listener to track layout changes on this view. If we don't get a callback, 889 // then there's nothing to animate. 890 final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() { 891 public void onLayoutChange(View v, int left, int top, int right, int bottom, 892 int oldLeft, int oldTop, int oldRight, int oldBottom) { 893 894 // Tell the animation to extract end values from the changed object 895 anim.setupEndValues(); 896 if (anim instanceof ValueAnimator) { 897 boolean valuesDiffer = false; 898 ValueAnimator valueAnim = (ValueAnimator)anim; 899 PropertyValuesHolder[] oldValues = valueAnim.getValues(); 900 for (int i = 0; i < oldValues.length; ++i) { 901 PropertyValuesHolder pvh = oldValues[i]; 902 KeyframeSet keyframeSet = pvh.mKeyframeSet; 903 if (keyframeSet.mFirstKeyframe == null || 904 keyframeSet.mLastKeyframe == null || 905 !keyframeSet.mFirstKeyframe.getValue().equals( 906 keyframeSet.mLastKeyframe.getValue())) { 907 valuesDiffer = true; 908 } 909 } 910 if (!valuesDiffer) { 911 return; 912 } 913 } 914 915 long startDelay = 0; 916 switch (changeReason) { 917 case APPEARING: 918 startDelay = mChangingAppearingDelay + staggerDelay; 919 staggerDelay += mChangingAppearingStagger; 920 if (mChangingAppearingInterpolator != sChangingAppearingInterpolator) { 921 anim.setInterpolator(mChangingAppearingInterpolator); 922 } 923 break; 924 case DISAPPEARING: 925 startDelay = mChangingDisappearingDelay + staggerDelay; 926 staggerDelay += mChangingDisappearingStagger; 927 if (mChangingDisappearingInterpolator != 928 sChangingDisappearingInterpolator) { 929 anim.setInterpolator(mChangingDisappearingInterpolator); 930 } 931 break; 932 case CHANGING: 933 startDelay = mChangingDelay + staggerDelay; 934 staggerDelay += mChangingStagger; 935 if (mChangingInterpolator != sChangingInterpolator) { 936 anim.setInterpolator(mChangingInterpolator); 937 } 938 break; 939 } 940 anim.setStartDelay(startDelay); 941 anim.setDuration(duration); 942 943 Animator prevAnimation = currentChangingAnimations.get(child); 944 if (prevAnimation != null) { 945 prevAnimation.cancel(); 946 } 947 Animator pendingAnimation = pendingAnimations.get(child); 948 if (pendingAnimation != null) { 949 pendingAnimations.remove(child); 950 } 951 // Cache the animation in case we need to cancel it later 952 currentChangingAnimations.put(child, anim); 953 954 parent.requestTransitionStart(LayoutTransition.this); 955 956 // this only removes listeners whose views changed - must clear the 957 // other listeners later 958 child.removeOnLayoutChangeListener(this); 959 layoutChangeListenerMap.remove(child); 960 } 961 }; 962 // Remove the animation from the cache when it ends 963 anim.addListener(new AnimatorListenerAdapter() { 964 965 @Override 966 public void onAnimationStart(Animator animator) { 967 if (hasListeners()) { 968 ArrayList<TransitionListener> listeners = 969 (ArrayList<TransitionListener>) mListeners.clone(); 970 for (TransitionListener listener : listeners) { 971 listener.startTransition(LayoutTransition.this, parent, child, 972 changeReason == APPEARING ? 973 CHANGE_APPEARING : changeReason == DISAPPEARING ? 974 CHANGE_DISAPPEARING : CHANGING); 975 } 976 } 977 } 978 979 @Override 980 public void onAnimationCancel(Animator animator) { 981 child.removeOnLayoutChangeListener(listener); 982 layoutChangeListenerMap.remove(child); 983 } 984 985 @Override 986 public void onAnimationEnd(Animator animator) { 987 currentChangingAnimations.remove(child); 988 if (hasListeners()) { 989 ArrayList<TransitionListener> listeners = 990 (ArrayList<TransitionListener>) mListeners.clone(); 991 for (TransitionListener listener : listeners) { 992 listener.endTransition(LayoutTransition.this, parent, child, 993 changeReason == APPEARING ? 994 CHANGE_APPEARING : changeReason == DISAPPEARING ? 995 CHANGE_DISAPPEARING : CHANGING); 996 } 997 } 998 } 999 }); 1000 1001 child.addOnLayoutChangeListener(listener); 1002 // cache the listener for later removal 1003 layoutChangeListenerMap.put(child, listener); 1004 } 1005 1006 /** 1007 * Starts the animations set up for a CHANGING transition. We separate the setup of these 1008 * animations from actually starting them, to avoid side-effects that starting the animations 1009 * may have on the properties of the affected objects. After setup, we tell the affected parent 1010 * that this transition should be started. The parent informs its ViewAncestor, which then 1011 * starts the transition after the current layout/measurement phase, just prior to drawing 1012 * the view hierarchy. 1013 * 1014 * @hide 1015 */ 1016 public void startChangingAnimations() { 1017 LinkedHashMap<View, Animator> currentAnimCopy = 1018 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 1019 for (Animator anim : currentAnimCopy.values()) { 1020 if (anim instanceof ObjectAnimator) { 1021 ((ObjectAnimator) anim).setCurrentPlayTime(0); 1022 } 1023 anim.start(); 1024 } 1025 } 1026 1027 /** 1028 * Ends the animations that are set up for a CHANGING transition. This is a variant of 1029 * startChangingAnimations() which is called when the window the transition is playing in 1030 * is not visible. We need to make sure the animations put their targets in their end states 1031 * and that the transition finishes to remove any mid-process state (such as isRunning()). 1032 * 1033 * @hide 1034 */ 1035 public void endChangingAnimations() { 1036 LinkedHashMap<View, Animator> currentAnimCopy = 1037 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 1038 for (Animator anim : currentAnimCopy.values()) { 1039 anim.start(); 1040 anim.end(); 1041 } 1042 // listeners should clean up the currentChangingAnimations list, but just in case... 1043 currentChangingAnimations.clear(); 1044 } 1045 1046 /** 1047 * Returns true if animations are running which animate layout-related properties. This 1048 * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations 1049 * are running, since these animations operate on layout-related properties. 1050 * 1051 * @return true if CHANGE_APPEARING or CHANGE_DISAPPEARING animations are currently 1052 * running. 1053 */ 1054 public boolean isChangingLayout() { 1055 return (currentChangingAnimations.size() > 0); 1056 } 1057 1058 /** 1059 * Returns true if any of the animations in this transition are currently running. 1060 * 1061 * @return true if any animations in the transition are running. 1062 */ 1063 public boolean isRunning() { 1064 return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 || 1065 currentDisappearingAnimations.size() > 0); 1066 } 1067 1068 /** 1069 * Cancels the currently running transition. Note that we cancel() the changing animations 1070 * but end() the visibility animations. This is because this method is currently called 1071 * in the context of starting a new transition, so we want to move things from their mid- 1072 * transition positions, but we want them to have their end-transition visibility. 1073 * 1074 * @hide 1075 */ 1076 public void cancel() { 1077 if (currentChangingAnimations.size() > 0) { 1078 LinkedHashMap<View, Animator> currentAnimCopy = 1079 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 1080 for (Animator anim : currentAnimCopy.values()) { 1081 anim.cancel(); 1082 } 1083 currentChangingAnimations.clear(); 1084 } 1085 if (currentAppearingAnimations.size() > 0) { 1086 LinkedHashMap<View, Animator> currentAnimCopy = 1087 (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone(); 1088 for (Animator anim : currentAnimCopy.values()) { 1089 anim.end(); 1090 } 1091 currentAppearingAnimations.clear(); 1092 } 1093 if (currentDisappearingAnimations.size() > 0) { 1094 LinkedHashMap<View, Animator> currentAnimCopy = 1095 (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone(); 1096 for (Animator anim : currentAnimCopy.values()) { 1097 anim.end(); 1098 } 1099 currentDisappearingAnimations.clear(); 1100 } 1101 } 1102 1103 /** 1104 * Cancels the specified type of transition. Note that we cancel() the changing animations 1105 * but end() the visibility animations. This is because this method is currently called 1106 * in the context of starting a new transition, so we want to move things from their mid- 1107 * transition positions, but we want them to have their end-transition visibility. 1108 * 1109 * @hide 1110 */ 1111 public void cancel(int transitionType) { 1112 switch (transitionType) { 1113 case CHANGE_APPEARING: 1114 case CHANGE_DISAPPEARING: 1115 case CHANGING: 1116 if (currentChangingAnimations.size() > 0) { 1117 LinkedHashMap<View, Animator> currentAnimCopy = 1118 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 1119 for (Animator anim : currentAnimCopy.values()) { 1120 anim.cancel(); 1121 } 1122 currentChangingAnimations.clear(); 1123 } 1124 break; 1125 case APPEARING: 1126 if (currentAppearingAnimations.size() > 0) { 1127 LinkedHashMap<View, Animator> currentAnimCopy = 1128 (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone(); 1129 for (Animator anim : currentAnimCopy.values()) { 1130 anim.end(); 1131 } 1132 currentAppearingAnimations.clear(); 1133 } 1134 break; 1135 case DISAPPEARING: 1136 if (currentDisappearingAnimations.size() > 0) { 1137 LinkedHashMap<View, Animator> currentAnimCopy = 1138 (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone(); 1139 for (Animator anim : currentAnimCopy.values()) { 1140 anim.end(); 1141 } 1142 currentDisappearingAnimations.clear(); 1143 } 1144 break; 1145 } 1146 } 1147 1148 /** 1149 * This method runs the animation that makes an added item appear. 1150 * 1151 * @param parent The ViewGroup to which the View is being added. 1152 * @param child The View being added to the ViewGroup. 1153 */ 1154 private void runAppearingTransition(final ViewGroup parent, final View child) { 1155 Animator currentAnimation = currentDisappearingAnimations.get(child); 1156 if (currentAnimation != null) { 1157 currentAnimation.cancel(); 1158 } 1159 if (mAppearingAnim == null) { 1160 if (hasListeners()) { 1161 ArrayList<TransitionListener> listeners = 1162 (ArrayList<TransitionListener>) mListeners.clone(); 1163 for (TransitionListener listener : listeners) { 1164 listener.endTransition(LayoutTransition.this, parent, child, APPEARING); 1165 } 1166 } 1167 return; 1168 } 1169 Animator anim = mAppearingAnim.clone(); 1170 anim.setTarget(child); 1171 anim.setStartDelay(mAppearingDelay); 1172 anim.setDuration(mAppearingDuration); 1173 if (mAppearingInterpolator != sAppearingInterpolator) { 1174 anim.setInterpolator(mAppearingInterpolator); 1175 } 1176 if (anim instanceof ObjectAnimator) { 1177 ((ObjectAnimator) anim).setCurrentPlayTime(0); 1178 } 1179 anim.addListener(new AnimatorListenerAdapter() { 1180 @Override 1181 public void onAnimationEnd(Animator anim) { 1182 currentAppearingAnimations.remove(child); 1183 if (hasListeners()) { 1184 ArrayList<TransitionListener> listeners = 1185 (ArrayList<TransitionListener>) mListeners.clone(); 1186 for (TransitionListener listener : listeners) { 1187 listener.endTransition(LayoutTransition.this, parent, child, APPEARING); 1188 } 1189 } 1190 } 1191 }); 1192 currentAppearingAnimations.put(child, anim); 1193 anim.start(); 1194 } 1195 1196 /** 1197 * This method runs the animation that makes a removed item disappear. 1198 * 1199 * @param parent The ViewGroup from which the View is being removed. 1200 * @param child The View being removed from the ViewGroup. 1201 */ 1202 private void runDisappearingTransition(final ViewGroup parent, final View child) { 1203 Animator currentAnimation = currentAppearingAnimations.get(child); 1204 if (currentAnimation != null) { 1205 currentAnimation.cancel(); 1206 } 1207 if (mDisappearingAnim == null) { 1208 if (hasListeners()) { 1209 ArrayList<TransitionListener> listeners = 1210 (ArrayList<TransitionListener>) mListeners.clone(); 1211 for (TransitionListener listener : listeners) { 1212 listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); 1213 } 1214 } 1215 return; 1216 } 1217 Animator anim = mDisappearingAnim.clone(); 1218 anim.setStartDelay(mDisappearingDelay); 1219 anim.setDuration(mDisappearingDuration); 1220 if (mDisappearingInterpolator != sDisappearingInterpolator) { 1221 anim.setInterpolator(mDisappearingInterpolator); 1222 } 1223 anim.setTarget(child); 1224 final float preAnimAlpha = child.getAlpha(); 1225 anim.addListener(new AnimatorListenerAdapter() { 1226 @Override 1227 public void onAnimationEnd(Animator anim) { 1228 currentDisappearingAnimations.remove(child); 1229 child.setAlpha(preAnimAlpha); 1230 if (hasListeners()) { 1231 ArrayList<TransitionListener> listeners = 1232 (ArrayList<TransitionListener>) mListeners.clone(); 1233 for (TransitionListener listener : listeners) { 1234 listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); 1235 } 1236 } 1237 } 1238 }); 1239 if (anim instanceof ObjectAnimator) { 1240 ((ObjectAnimator) anim).setCurrentPlayTime(0); 1241 } 1242 currentDisappearingAnimations.put(child, anim); 1243 anim.start(); 1244 } 1245 1246 /** 1247 * This method is called by ViewGroup when a child view is about to be added to the 1248 * container. This callback starts the process of a transition; we grab the starting 1249 * values, listen for changes to all of the children of the container, and start appropriate 1250 * animations. 1251 * 1252 * @param parent The ViewGroup to which the View is being added. 1253 * @param child The View being added to the ViewGroup. 1254 * @param changesLayout Whether the removal will cause changes in the layout of other views 1255 * in the container. INVISIBLE views becoming VISIBLE will not cause changes and thus will not 1256 * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations. 1257 */ 1258 private void addChild(ViewGroup parent, View child, boolean changesLayout) { 1259 if (parent.getWindowVisibility() != View.VISIBLE) { 1260 return; 1261 } 1262 if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) { 1263 // Want disappearing animations to finish up before proceeding 1264 cancel(DISAPPEARING); 1265 } 1266 if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) { 1267 // Also, cancel changing animations so that we start fresh ones from current locations 1268 cancel(CHANGE_APPEARING); 1269 cancel(CHANGING); 1270 } 1271 if (hasListeners() && (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) { 1272 ArrayList<TransitionListener> listeners = 1273 (ArrayList<TransitionListener>) mListeners.clone(); 1274 for (TransitionListener listener : listeners) { 1275 listener.startTransition(this, parent, child, APPEARING); 1276 } 1277 } 1278 if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) { 1279 runChangeTransition(parent, child, APPEARING); 1280 } 1281 if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) { 1282 runAppearingTransition(parent, child); 1283 } 1284 } 1285 1286 private boolean hasListeners() { 1287 return mListeners != null && mListeners.size() > 0; 1288 } 1289 1290 /** 1291 * This method is called by ViewGroup when there is a call to layout() on the container 1292 * with this LayoutTransition. If the CHANGING transition is enabled and if there is no other 1293 * transition currently running on the container, then this call runs a CHANGING transition. 1294 * The transition does not start immediately; it just sets up the mechanism to run if any 1295 * of the children of the container change their layout parameters (similar to 1296 * the CHANGE_APPEARING and CHANGE_DISAPPEARING transitions). 1297 * 1298 * @param parent The ViewGroup whose layout() method has been called. 1299 * 1300 * @hide 1301 */ 1302 public void layoutChange(ViewGroup parent) { 1303 if (parent.getWindowVisibility() != View.VISIBLE) { 1304 return; 1305 } 1306 if ((mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING && !isRunning()) { 1307 // This method is called for all calls to layout() in the container, including 1308 // those caused by add/remove/hide/show events, which will already have set up 1309 // transition animations. Avoid setting up CHANGING animations in this case; only 1310 // do so when there is not a transition already running on the container. 1311 runChangeTransition(parent, null, CHANGING); 1312 } 1313 } 1314 1315 /** 1316 * This method is called by ViewGroup when a child view is about to be added to the 1317 * container. This callback starts the process of a transition; we grab the starting 1318 * values, listen for changes to all of the children of the container, and start appropriate 1319 * animations. 1320 * 1321 * @param parent The ViewGroup to which the View is being added. 1322 * @param child The View being added to the ViewGroup. 1323 */ 1324 public void addChild(ViewGroup parent, View child) { 1325 addChild(parent, child, true); 1326 } 1327 1328 /** 1329 * @deprecated Use {@link #showChild(android.view.ViewGroup, android.view.View, int)}. 1330 */ 1331 @Deprecated 1332 public void showChild(ViewGroup parent, View child) { 1333 addChild(parent, child, true); 1334 } 1335 1336 /** 1337 * This method is called by ViewGroup when a child view is about to be made visible in the 1338 * container. This callback starts the process of a transition; we grab the starting 1339 * values, listen for changes to all of the children of the container, and start appropriate 1340 * animations. 1341 * 1342 * @param parent The ViewGroup in which the View is being made visible. 1343 * @param child The View being made visible. 1344 * @param oldVisibility The previous visibility value of the child View, either 1345 * {@link View#GONE} or {@link View#INVISIBLE}. 1346 */ 1347 public void showChild(ViewGroup parent, View child, int oldVisibility) { 1348 addChild(parent, child, oldVisibility == View.GONE); 1349 } 1350 1351 /** 1352 * This method is called by ViewGroup when a child view is about to be removed from the 1353 * container. This callback starts the process of a transition; we grab the starting 1354 * values, listen for changes to all of the children of the container, and start appropriate 1355 * animations. 1356 * 1357 * @param parent The ViewGroup from which the View is being removed. 1358 * @param child The View being removed from the ViewGroup. 1359 * @param changesLayout Whether the removal will cause changes in the layout of other views 1360 * in the container. Views becoming INVISIBLE will not cause changes and thus will not 1361 * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations. 1362 */ 1363 private void removeChild(ViewGroup parent, View child, boolean changesLayout) { 1364 if (parent.getWindowVisibility() != View.VISIBLE) { 1365 return; 1366 } 1367 if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) { 1368 // Want appearing animations to finish up before proceeding 1369 cancel(APPEARING); 1370 } 1371 if (changesLayout && 1372 (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) { 1373 // Also, cancel changing animations so that we start fresh ones from current locations 1374 cancel(CHANGE_DISAPPEARING); 1375 cancel(CHANGING); 1376 } 1377 if (hasListeners() && (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) { 1378 ArrayList<TransitionListener> listeners = (ArrayList<TransitionListener>) mListeners 1379 .clone(); 1380 for (TransitionListener listener : listeners) { 1381 listener.startTransition(this, parent, child, DISAPPEARING); 1382 } 1383 } 1384 if (changesLayout && 1385 (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) { 1386 runChangeTransition(parent, child, DISAPPEARING); 1387 } 1388 if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) { 1389 runDisappearingTransition(parent, child); 1390 } 1391 } 1392 1393 /** 1394 * This method is called by ViewGroup when a child view is about to be removed from the 1395 * container. This callback starts the process of a transition; we grab the starting 1396 * values, listen for changes to all of the children of the container, and start appropriate 1397 * animations. 1398 * 1399 * @param parent The ViewGroup from which the View is being removed. 1400 * @param child The View being removed from the ViewGroup. 1401 */ 1402 public void removeChild(ViewGroup parent, View child) { 1403 removeChild(parent, child, true); 1404 } 1405 1406 /** 1407 * @deprecated Use {@link #hideChild(android.view.ViewGroup, android.view.View, int)}. 1408 */ 1409 @Deprecated 1410 public void hideChild(ViewGroup parent, View child) { 1411 removeChild(parent, child, true); 1412 } 1413 1414 /** 1415 * This method is called by ViewGroup when a child view is about to be hidden in 1416 * container. This callback starts the process of a transition; we grab the starting 1417 * values, listen for changes to all of the children of the container, and start appropriate 1418 * animations. 1419 * 1420 * @param parent The parent ViewGroup of the View being hidden. 1421 * @param child The View being hidden. 1422 * @param newVisibility The new visibility value of the child View, either 1423 * {@link View#GONE} or {@link View#INVISIBLE}. 1424 */ 1425 public void hideChild(ViewGroup parent, View child, int newVisibility) { 1426 removeChild(parent, child, newVisibility == View.GONE); 1427 } 1428 1429 /** 1430 * Add a listener that will be called when the bounds of the view change due to 1431 * layout processing. 1432 * 1433 * @param listener The listener that will be called when layout bounds change. 1434 */ 1435 public void addTransitionListener(TransitionListener listener) { 1436 if (mListeners == null) { 1437 mListeners = new ArrayList<TransitionListener>(); 1438 } 1439 mListeners.add(listener); 1440 } 1441 1442 /** 1443 * Remove a listener for layout changes. 1444 * 1445 * @param listener The listener for layout bounds change. 1446 */ 1447 public void removeTransitionListener(TransitionListener listener) { 1448 if (mListeners == null) { 1449 return; 1450 } 1451 mListeners.remove(listener); 1452 } 1453 1454 /** 1455 * Gets the current list of listeners for layout changes. 1456 * @return 1457 */ 1458 public List<TransitionListener> getTransitionListeners() { 1459 return mListeners; 1460 } 1461 1462 /** 1463 * This interface is used for listening to starting and ending events for transitions. 1464 */ 1465 public interface TransitionListener { 1466 1467 /** 1468 * This event is sent to listeners when any type of transition animation begins. 1469 * 1470 * @param transition The LayoutTransition sending out the event. 1471 * @param container The ViewGroup on which the transition is playing. 1472 * @param view The View object being affected by the transition animation. 1473 * @param transitionType The type of transition that is beginning, 1474 * {@link android.animation.LayoutTransition#APPEARING}, 1475 * {@link android.animation.LayoutTransition#DISAPPEARING}, 1476 * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or 1477 * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}. 1478 */ 1479 public void startTransition(LayoutTransition transition, ViewGroup container, 1480 View view, int transitionType); 1481 1482 /** 1483 * This event is sent to listeners when any type of transition animation ends. 1484 * 1485 * @param transition The LayoutTransition sending out the event. 1486 * @param container The ViewGroup on which the transition is playing. 1487 * @param view The View object being affected by the transition animation. 1488 * @param transitionType The type of transition that is ending, 1489 * {@link android.animation.LayoutTransition#APPEARING}, 1490 * {@link android.animation.LayoutTransition#DISAPPEARING}, 1491 * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or 1492 * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}. 1493 */ 1494 public void endTransition(LayoutTransition transition, ViewGroup container, 1495 View view, int transitionType); 1496 } 1497 1498 } 1499