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