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 if (pvh.mKeyframes instanceof KeyframeSet) { 903 KeyframeSet keyframeSet = (KeyframeSet) pvh.mKeyframes; 904 if (keyframeSet.mFirstKeyframe == null || 905 keyframeSet.mLastKeyframe == null || 906 !keyframeSet.mFirstKeyframe.getValue().equals( 907 keyframeSet.mLastKeyframe.getValue())) { 908 valuesDiffer = true; 909 } 910 } else if (!pvh.mKeyframes.getValue(0).equals(pvh.mKeyframes.getValue(1))) { 911 valuesDiffer = true; 912 } 913 } 914 if (!valuesDiffer) { 915 return; 916 } 917 } 918 919 long startDelay = 0; 920 switch (changeReason) { 921 case APPEARING: 922 startDelay = mChangingAppearingDelay + staggerDelay; 923 staggerDelay += mChangingAppearingStagger; 924 if (mChangingAppearingInterpolator != sChangingAppearingInterpolator) { 925 anim.setInterpolator(mChangingAppearingInterpolator); 926 } 927 break; 928 case DISAPPEARING: 929 startDelay = mChangingDisappearingDelay + staggerDelay; 930 staggerDelay += mChangingDisappearingStagger; 931 if (mChangingDisappearingInterpolator != 932 sChangingDisappearingInterpolator) { 933 anim.setInterpolator(mChangingDisappearingInterpolator); 934 } 935 break; 936 case CHANGING: 937 startDelay = mChangingDelay + staggerDelay; 938 staggerDelay += mChangingStagger; 939 if (mChangingInterpolator != sChangingInterpolator) { 940 anim.setInterpolator(mChangingInterpolator); 941 } 942 break; 943 } 944 anim.setStartDelay(startDelay); 945 anim.setDuration(duration); 946 947 Animator prevAnimation = currentChangingAnimations.get(child); 948 if (prevAnimation != null) { 949 prevAnimation.cancel(); 950 } 951 Animator pendingAnimation = pendingAnimations.get(child); 952 if (pendingAnimation != null) { 953 pendingAnimations.remove(child); 954 } 955 // Cache the animation in case we need to cancel it later 956 currentChangingAnimations.put(child, anim); 957 958 parent.requestTransitionStart(LayoutTransition.this); 959 960 // this only removes listeners whose views changed - must clear the 961 // other listeners later 962 child.removeOnLayoutChangeListener(this); 963 layoutChangeListenerMap.remove(child); 964 } 965 }; 966 // Remove the animation from the cache when it ends 967 anim.addListener(new AnimatorListenerAdapter() { 968 969 @Override 970 public void onAnimationStart(Animator animator) { 971 if (hasListeners()) { 972 ArrayList<TransitionListener> listeners = 973 (ArrayList<TransitionListener>) mListeners.clone(); 974 for (TransitionListener listener : listeners) { 975 listener.startTransition(LayoutTransition.this, parent, child, 976 changeReason == APPEARING ? 977 CHANGE_APPEARING : changeReason == DISAPPEARING ? 978 CHANGE_DISAPPEARING : CHANGING); 979 } 980 } 981 } 982 983 @Override 984 public void onAnimationCancel(Animator animator) { 985 child.removeOnLayoutChangeListener(listener); 986 layoutChangeListenerMap.remove(child); 987 } 988 989 @Override 990 public void onAnimationEnd(Animator animator) { 991 currentChangingAnimations.remove(child); 992 if (hasListeners()) { 993 ArrayList<TransitionListener> listeners = 994 (ArrayList<TransitionListener>) mListeners.clone(); 995 for (TransitionListener listener : listeners) { 996 listener.endTransition(LayoutTransition.this, parent, child, 997 changeReason == APPEARING ? 998 CHANGE_APPEARING : changeReason == DISAPPEARING ? 999 CHANGE_DISAPPEARING : CHANGING); 1000 } 1001 } 1002 } 1003 }); 1004 1005 child.addOnLayoutChangeListener(listener); 1006 // cache the listener for later removal 1007 layoutChangeListenerMap.put(child, listener); 1008 } 1009 1010 /** 1011 * Starts the animations set up for a CHANGING transition. We separate the setup of these 1012 * animations from actually starting them, to avoid side-effects that starting the animations 1013 * may have on the properties of the affected objects. After setup, we tell the affected parent 1014 * that this transition should be started. The parent informs its ViewAncestor, which then 1015 * starts the transition after the current layout/measurement phase, just prior to drawing 1016 * the view hierarchy. 1017 * 1018 * @hide 1019 */ 1020 public void startChangingAnimations() { 1021 LinkedHashMap<View, Animator> currentAnimCopy = 1022 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 1023 for (Animator anim : currentAnimCopy.values()) { 1024 if (anim instanceof ObjectAnimator) { 1025 ((ObjectAnimator) anim).setCurrentPlayTime(0); 1026 } 1027 anim.start(); 1028 } 1029 } 1030 1031 /** 1032 * Ends the animations that are set up for a CHANGING transition. This is a variant of 1033 * startChangingAnimations() which is called when the window the transition is playing in 1034 * is not visible. We need to make sure the animations put their targets in their end states 1035 * and that the transition finishes to remove any mid-process state (such as isRunning()). 1036 * 1037 * @hide 1038 */ 1039 public void endChangingAnimations() { 1040 LinkedHashMap<View, Animator> currentAnimCopy = 1041 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 1042 for (Animator anim : currentAnimCopy.values()) { 1043 anim.start(); 1044 anim.end(); 1045 } 1046 // listeners should clean up the currentChangingAnimations list, but just in case... 1047 currentChangingAnimations.clear(); 1048 } 1049 1050 /** 1051 * Returns true if animations are running which animate layout-related properties. This 1052 * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations 1053 * are running, since these animations operate on layout-related properties. 1054 * 1055 * @return true if CHANGE_APPEARING or CHANGE_DISAPPEARING animations are currently 1056 * running. 1057 */ 1058 public boolean isChangingLayout() { 1059 return (currentChangingAnimations.size() > 0); 1060 } 1061 1062 /** 1063 * Returns true if any of the animations in this transition are currently running. 1064 * 1065 * @return true if any animations in the transition are running. 1066 */ 1067 public boolean isRunning() { 1068 return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 || 1069 currentDisappearingAnimations.size() > 0); 1070 } 1071 1072 /** 1073 * Cancels the currently running transition. Note that we cancel() the changing animations 1074 * but end() the visibility animations. This is because this method is currently called 1075 * in the context of starting a new transition, so we want to move things from their mid- 1076 * transition positions, but we want them to have their end-transition visibility. 1077 * 1078 * @hide 1079 */ 1080 public void cancel() { 1081 if (currentChangingAnimations.size() > 0) { 1082 LinkedHashMap<View, Animator> currentAnimCopy = 1083 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 1084 for (Animator anim : currentAnimCopy.values()) { 1085 anim.cancel(); 1086 } 1087 currentChangingAnimations.clear(); 1088 } 1089 if (currentAppearingAnimations.size() > 0) { 1090 LinkedHashMap<View, Animator> currentAnimCopy = 1091 (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone(); 1092 for (Animator anim : currentAnimCopy.values()) { 1093 anim.end(); 1094 } 1095 currentAppearingAnimations.clear(); 1096 } 1097 if (currentDisappearingAnimations.size() > 0) { 1098 LinkedHashMap<View, Animator> currentAnimCopy = 1099 (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone(); 1100 for (Animator anim : currentAnimCopy.values()) { 1101 anim.end(); 1102 } 1103 currentDisappearingAnimations.clear(); 1104 } 1105 } 1106 1107 /** 1108 * Cancels the specified type of transition. Note that we cancel() the changing animations 1109 * but end() the visibility animations. This is because this method is currently called 1110 * in the context of starting a new transition, so we want to move things from their mid- 1111 * transition positions, but we want them to have their end-transition visibility. 1112 * 1113 * @hide 1114 */ 1115 public void cancel(int transitionType) { 1116 switch (transitionType) { 1117 case CHANGE_APPEARING: 1118 case CHANGE_DISAPPEARING: 1119 case CHANGING: 1120 if (currentChangingAnimations.size() > 0) { 1121 LinkedHashMap<View, Animator> currentAnimCopy = 1122 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 1123 for (Animator anim : currentAnimCopy.values()) { 1124 anim.cancel(); 1125 } 1126 currentChangingAnimations.clear(); 1127 } 1128 break; 1129 case APPEARING: 1130 if (currentAppearingAnimations.size() > 0) { 1131 LinkedHashMap<View, Animator> currentAnimCopy = 1132 (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone(); 1133 for (Animator anim : currentAnimCopy.values()) { 1134 anim.end(); 1135 } 1136 currentAppearingAnimations.clear(); 1137 } 1138 break; 1139 case DISAPPEARING: 1140 if (currentDisappearingAnimations.size() > 0) { 1141 LinkedHashMap<View, Animator> currentAnimCopy = 1142 (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone(); 1143 for (Animator anim : currentAnimCopy.values()) { 1144 anim.end(); 1145 } 1146 currentDisappearingAnimations.clear(); 1147 } 1148 break; 1149 } 1150 } 1151 1152 /** 1153 * This method runs the animation that makes an added item appear. 1154 * 1155 * @param parent The ViewGroup to which the View is being added. 1156 * @param child The View being added to the ViewGroup. 1157 */ 1158 private void runAppearingTransition(final ViewGroup parent, final View child) { 1159 Animator currentAnimation = currentDisappearingAnimations.get(child); 1160 if (currentAnimation != null) { 1161 currentAnimation.cancel(); 1162 } 1163 if (mAppearingAnim == null) { 1164 if (hasListeners()) { 1165 ArrayList<TransitionListener> listeners = 1166 (ArrayList<TransitionListener>) mListeners.clone(); 1167 for (TransitionListener listener : listeners) { 1168 listener.endTransition(LayoutTransition.this, parent, child, APPEARING); 1169 } 1170 } 1171 return; 1172 } 1173 Animator anim = mAppearingAnim.clone(); 1174 anim.setTarget(child); 1175 anim.setStartDelay(mAppearingDelay); 1176 anim.setDuration(mAppearingDuration); 1177 if (mAppearingInterpolator != sAppearingInterpolator) { 1178 anim.setInterpolator(mAppearingInterpolator); 1179 } 1180 if (anim instanceof ObjectAnimator) { 1181 ((ObjectAnimator) anim).setCurrentPlayTime(0); 1182 } 1183 anim.addListener(new AnimatorListenerAdapter() { 1184 @Override 1185 public void onAnimationEnd(Animator anim) { 1186 currentAppearingAnimations.remove(child); 1187 if (hasListeners()) { 1188 ArrayList<TransitionListener> listeners = 1189 (ArrayList<TransitionListener>) mListeners.clone(); 1190 for (TransitionListener listener : listeners) { 1191 listener.endTransition(LayoutTransition.this, parent, child, APPEARING); 1192 } 1193 } 1194 } 1195 }); 1196 currentAppearingAnimations.put(child, anim); 1197 anim.start(); 1198 } 1199 1200 /** 1201 * This method runs the animation that makes a removed item disappear. 1202 * 1203 * @param parent The ViewGroup from which the View is being removed. 1204 * @param child The View being removed from the ViewGroup. 1205 */ 1206 private void runDisappearingTransition(final ViewGroup parent, final View child) { 1207 Animator currentAnimation = currentAppearingAnimations.get(child); 1208 if (currentAnimation != null) { 1209 currentAnimation.cancel(); 1210 } 1211 if (mDisappearingAnim == null) { 1212 if (hasListeners()) { 1213 ArrayList<TransitionListener> listeners = 1214 (ArrayList<TransitionListener>) mListeners.clone(); 1215 for (TransitionListener listener : listeners) { 1216 listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); 1217 } 1218 } 1219 return; 1220 } 1221 Animator anim = mDisappearingAnim.clone(); 1222 anim.setStartDelay(mDisappearingDelay); 1223 anim.setDuration(mDisappearingDuration); 1224 if (mDisappearingInterpolator != sDisappearingInterpolator) { 1225 anim.setInterpolator(mDisappearingInterpolator); 1226 } 1227 anim.setTarget(child); 1228 final float preAnimAlpha = child.getAlpha(); 1229 anim.addListener(new AnimatorListenerAdapter() { 1230 @Override 1231 public void onAnimationEnd(Animator anim) { 1232 currentDisappearingAnimations.remove(child); 1233 child.setAlpha(preAnimAlpha); 1234 if (hasListeners()) { 1235 ArrayList<TransitionListener> listeners = 1236 (ArrayList<TransitionListener>) mListeners.clone(); 1237 for (TransitionListener listener : listeners) { 1238 listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); 1239 } 1240 } 1241 } 1242 }); 1243 if (anim instanceof ObjectAnimator) { 1244 ((ObjectAnimator) anim).setCurrentPlayTime(0); 1245 } 1246 currentDisappearingAnimations.put(child, anim); 1247 anim.start(); 1248 } 1249 1250 /** 1251 * This method is called by ViewGroup when a child view is about to be added to the 1252 * container. This callback starts the process of a transition; we grab the starting 1253 * values, listen for changes to all of the children of the container, and start appropriate 1254 * animations. 1255 * 1256 * @param parent The ViewGroup to which the View is being added. 1257 * @param child The View being added to the ViewGroup. 1258 * @param changesLayout Whether the removal will cause changes in the layout of other views 1259 * in the container. INVISIBLE views becoming VISIBLE will not cause changes and thus will not 1260 * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations. 1261 */ 1262 private void addChild(ViewGroup parent, View child, boolean changesLayout) { 1263 if (parent.getWindowVisibility() != View.VISIBLE) { 1264 return; 1265 } 1266 if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) { 1267 // Want disappearing animations to finish up before proceeding 1268 cancel(DISAPPEARING); 1269 } 1270 if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) { 1271 // Also, cancel changing animations so that we start fresh ones from current locations 1272 cancel(CHANGE_APPEARING); 1273 cancel(CHANGING); 1274 } 1275 if (hasListeners() && (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) { 1276 ArrayList<TransitionListener> listeners = 1277 (ArrayList<TransitionListener>) mListeners.clone(); 1278 for (TransitionListener listener : listeners) { 1279 listener.startTransition(this, parent, child, APPEARING); 1280 } 1281 } 1282 if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) { 1283 runChangeTransition(parent, child, APPEARING); 1284 } 1285 if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) { 1286 runAppearingTransition(parent, child); 1287 } 1288 } 1289 1290 private boolean hasListeners() { 1291 return mListeners != null && mListeners.size() > 0; 1292 } 1293 1294 /** 1295 * This method is called by ViewGroup when there is a call to layout() on the container 1296 * with this LayoutTransition. If the CHANGING transition is enabled and if there is no other 1297 * transition currently running on the container, then this call runs a CHANGING transition. 1298 * The transition does not start immediately; it just sets up the mechanism to run if any 1299 * of the children of the container change their layout parameters (similar to 1300 * the CHANGE_APPEARING and CHANGE_DISAPPEARING transitions). 1301 * 1302 * @param parent The ViewGroup whose layout() method has been called. 1303 * 1304 * @hide 1305 */ 1306 public void layoutChange(ViewGroup parent) { 1307 if (parent.getWindowVisibility() != View.VISIBLE) { 1308 return; 1309 } 1310 if ((mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING && !isRunning()) { 1311 // This method is called for all calls to layout() in the container, including 1312 // those caused by add/remove/hide/show events, which will already have set up 1313 // transition animations. Avoid setting up CHANGING animations in this case; only 1314 // do so when there is not a transition already running on the container. 1315 runChangeTransition(parent, null, CHANGING); 1316 } 1317 } 1318 1319 /** 1320 * This method is called by ViewGroup when a child view is about to be added to the 1321 * container. This callback starts the process of a transition; we grab the starting 1322 * values, listen for changes to all of the children of the container, and start appropriate 1323 * animations. 1324 * 1325 * @param parent The ViewGroup to which the View is being added. 1326 * @param child The View being added to the ViewGroup. 1327 */ 1328 public void addChild(ViewGroup parent, View child) { 1329 addChild(parent, child, true); 1330 } 1331 1332 /** 1333 * @deprecated Use {@link #showChild(android.view.ViewGroup, android.view.View, int)}. 1334 */ 1335 @Deprecated 1336 public void showChild(ViewGroup parent, View child) { 1337 addChild(parent, child, true); 1338 } 1339 1340 /** 1341 * This method is called by ViewGroup when a child view is about to be made visible in the 1342 * container. This callback starts the process of a transition; we grab the starting 1343 * values, listen for changes to all of the children of the container, and start appropriate 1344 * animations. 1345 * 1346 * @param parent The ViewGroup in which the View is being made visible. 1347 * @param child The View being made visible. 1348 * @param oldVisibility The previous visibility value of the child View, either 1349 * {@link View#GONE} or {@link View#INVISIBLE}. 1350 */ 1351 public void showChild(ViewGroup parent, View child, int oldVisibility) { 1352 addChild(parent, child, oldVisibility == View.GONE); 1353 } 1354 1355 /** 1356 * This method is called by ViewGroup when a child view is about to be removed from the 1357 * container. This callback starts the process of a transition; we grab the starting 1358 * values, listen for changes to all of the children of the container, and start appropriate 1359 * animations. 1360 * 1361 * @param parent The ViewGroup from which the View is being removed. 1362 * @param child The View being removed from the ViewGroup. 1363 * @param changesLayout Whether the removal will cause changes in the layout of other views 1364 * in the container. Views becoming INVISIBLE will not cause changes and thus will not 1365 * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations. 1366 */ 1367 private void removeChild(ViewGroup parent, View child, boolean changesLayout) { 1368 if (parent.getWindowVisibility() != View.VISIBLE) { 1369 return; 1370 } 1371 if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) { 1372 // Want appearing animations to finish up before proceeding 1373 cancel(APPEARING); 1374 } 1375 if (changesLayout && 1376 (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) { 1377 // Also, cancel changing animations so that we start fresh ones from current locations 1378 cancel(CHANGE_DISAPPEARING); 1379 cancel(CHANGING); 1380 } 1381 if (hasListeners() && (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) { 1382 ArrayList<TransitionListener> listeners = (ArrayList<TransitionListener>) mListeners 1383 .clone(); 1384 for (TransitionListener listener : listeners) { 1385 listener.startTransition(this, parent, child, DISAPPEARING); 1386 } 1387 } 1388 if (changesLayout && 1389 (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) { 1390 runChangeTransition(parent, child, DISAPPEARING); 1391 } 1392 if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) { 1393 runDisappearingTransition(parent, child); 1394 } 1395 } 1396 1397 /** 1398 * This method is called by ViewGroup when a child view is about to be removed from the 1399 * container. This callback starts the process of a transition; we grab the starting 1400 * values, listen for changes to all of the children of the container, and start appropriate 1401 * animations. 1402 * 1403 * @param parent The ViewGroup from which the View is being removed. 1404 * @param child The View being removed from the ViewGroup. 1405 */ 1406 public void removeChild(ViewGroup parent, View child) { 1407 removeChild(parent, child, true); 1408 } 1409 1410 /** 1411 * @deprecated Use {@link #hideChild(android.view.ViewGroup, android.view.View, int)}. 1412 */ 1413 @Deprecated 1414 public void hideChild(ViewGroup parent, View child) { 1415 removeChild(parent, child, true); 1416 } 1417 1418 /** 1419 * This method is called by ViewGroup when a child view is about to be hidden in 1420 * container. This callback starts the process of a transition; we grab the starting 1421 * values, listen for changes to all of the children of the container, and start appropriate 1422 * animations. 1423 * 1424 * @param parent The parent ViewGroup of the View being hidden. 1425 * @param child The View being hidden. 1426 * @param newVisibility The new visibility value of the child View, either 1427 * {@link View#GONE} or {@link View#INVISIBLE}. 1428 */ 1429 public void hideChild(ViewGroup parent, View child, int newVisibility) { 1430 removeChild(parent, child, newVisibility == View.GONE); 1431 } 1432 1433 /** 1434 * Add a listener that will be called when the bounds of the view change due to 1435 * layout processing. 1436 * 1437 * @param listener The listener that will be called when layout bounds change. 1438 */ 1439 public void addTransitionListener(TransitionListener listener) { 1440 if (mListeners == null) { 1441 mListeners = new ArrayList<TransitionListener>(); 1442 } 1443 mListeners.add(listener); 1444 } 1445 1446 /** 1447 * Remove a listener for layout changes. 1448 * 1449 * @param listener The listener for layout bounds change. 1450 */ 1451 public void removeTransitionListener(TransitionListener listener) { 1452 if (mListeners == null) { 1453 return; 1454 } 1455 mListeners.remove(listener); 1456 } 1457 1458 /** 1459 * Gets the current list of listeners for layout changes. 1460 * @return 1461 */ 1462 public List<TransitionListener> getTransitionListeners() { 1463 return mListeners; 1464 } 1465 1466 /** 1467 * This interface is used for listening to starting and ending events for transitions. 1468 */ 1469 public interface TransitionListener { 1470 1471 /** 1472 * This event is sent to listeners when any type of transition animation begins. 1473 * 1474 * @param transition The LayoutTransition sending out the event. 1475 * @param container The ViewGroup on which the transition is playing. 1476 * @param view The View object being affected by the transition animation. 1477 * @param transitionType The type of transition that is beginning, 1478 * {@link android.animation.LayoutTransition#APPEARING}, 1479 * {@link android.animation.LayoutTransition#DISAPPEARING}, 1480 * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or 1481 * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}. 1482 */ 1483 public void startTransition(LayoutTransition transition, ViewGroup container, 1484 View view, int transitionType); 1485 1486 /** 1487 * This event is sent to listeners when any type of transition animation ends. 1488 * 1489 * @param transition The LayoutTransition sending out the event. 1490 * @param container The ViewGroup on which the transition is playing. 1491 * @param view The View object being affected by the transition animation. 1492 * @param transitionType The type of transition that is ending, 1493 * {@link android.animation.LayoutTransition#APPEARING}, 1494 * {@link android.animation.LayoutTransition#DISAPPEARING}, 1495 * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or 1496 * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}. 1497 */ 1498 public void endTransition(LayoutTransition transition, ViewGroup container, 1499 View view, int transitionType); 1500 } 1501 1502 } 1503