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