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