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 17package android.animation; 18 19import android.view.View; 20import android.view.ViewGroup; 21import android.view.ViewParent; 22import android.view.ViewTreeObserver; 23import android.view.animation.AccelerateDecelerateInterpolator; 24import android.view.animation.DecelerateInterpolator; 25 26import java.util.ArrayList; 27import java.util.Collection; 28import java.util.HashMap; 29import java.util.LinkedHashMap; 30import java.util.List; 31import 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 */ 100public 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 1506 CleanupCallback(Map<View, View.OnLayoutChangeListener> listenerMap, ViewGroup parent) { 1507 this.layoutChangeListenerMap = listenerMap; 1508 this.parent = parent; 1509 } 1510 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 1526 public void onViewAttachedToWindow(View v) { 1527 } 1528 1529 @Override 1530 public void onViewDetachedFromWindow(View v) { 1531 cleanup(); 1532 } 1533 1534 @Override 1535 public boolean onPreDraw() { 1536 cleanup(); 1537 return true; 1538 } 1539 }; 1540 1541} 1542