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