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