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 */ LayoutTransition()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 */ setDuration(long duration)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 */ enableTransitionType(int transitionType)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 */ disableTransitionType(int transitionType)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 */ isTransitionTypeEnabled(int transitionType)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 */ setStartDelay(int transitionType, long delay)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 */ getStartDelay(int transitionType)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 */ setDuration(int transitionType, long duration)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 */ getDuration(int transitionType)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 */ setStagger(int transitionType, long duration)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 */ getStagger(int transitionType)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 */ setInterpolator(int transitionType, TimeInterpolator interpolator)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 */ getInterpolator(int transitionType)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 */ setAnimator(int transitionType, Animator animator)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 */ getAnimator(int transitionType)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 */ runChangeTransition(final ViewGroup parent, View newView, final int changeReason)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 */ setAnimateParentHierarchy(boolean animateParentHierarchy)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 */ setupChangeAnimation(final ViewGroup parent, final int changeReason, Animator baseAnimator, final long duration, final View child)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 */ startChangingAnimations()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 */ endChangingAnimations()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 */ isChangingLayout()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 */ isRunning()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 */ cancel()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 */ cancel(int transitionType)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 */ runAppearingTransition(final ViewGroup parent, final View child)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 */ runDisappearingTransition(final ViewGroup parent, final View child)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 */ addChild(ViewGroup parent, View child, boolean changesLayout)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 hasListeners()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 */ layoutChange(ViewGroup parent)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 */ addChild(ViewGroup parent, View child)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 showChild(ViewGroup parent, View child)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 */ showChild(ViewGroup parent, View child, int oldVisibility)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 */ removeChild(ViewGroup parent, View child, boolean changesLayout)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 */ removeChild(ViewGroup parent, View child)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 hideChild(ViewGroup parent, View child)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 */ hideChild(ViewGroup parent, View child, int newVisibility)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 */ addTransitionListener(TransitionListener listener)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 */ removeTransitionListener(TransitionListener listener)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 */ getTransitionListeners()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 */ startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)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 */ endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)1466 public void endTransition(LayoutTransition transition, ViewGroup container, 1467 View view, int transitionType); 1468 } 1469 1470 } 1471