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