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 * These variables hold the animations that are currently used to run the transition effects. 123 * These animations are set to defaults, but can be changed to custom animations by 124 * calls to setAnimator(). 125 */ 126 private Animator mDisappearingAnim = null; 127 private Animator mAppearingAnim = null; 128 private Animator mChangingAppearingAnim = null; 129 private Animator mChangingDisappearingAnim = null; 130 131 /** 132 * These are the default animations, defined in the constructor, that will be used 133 * unless the user specifies custom animations. 134 */ 135 private static ObjectAnimator defaultChangeIn; 136 private static ObjectAnimator defaultChangeOut; 137 private static ObjectAnimator defaultFadeIn; 138 private static ObjectAnimator defaultFadeOut; 139 140 /** 141 * The default duration used by all animations. 142 */ 143 private static long DEFAULT_DURATION = 300; 144 145 /** 146 * The durations of the four different animations 147 */ 148 private long mChangingAppearingDuration = DEFAULT_DURATION; 149 private long mChangingDisappearingDuration = DEFAULT_DURATION; 150 private long mAppearingDuration = DEFAULT_DURATION; 151 private long mDisappearingDuration = DEFAULT_DURATION; 152 153 /** 154 * The start delays of the four different animations. Note that the default behavior of 155 * the appearing item is the default duration, since it should wait for the items to move 156 * before fading it. Same for the changing animation when disappearing; it waits for the item 157 * to fade out before moving the other items. 158 */ 159 private long mAppearingDelay = DEFAULT_DURATION; 160 private long mDisappearingDelay = 0; 161 private long mChangingAppearingDelay = 0; 162 private long mChangingDisappearingDelay = DEFAULT_DURATION; 163 164 /** 165 * The inter-animation delays used on the two changing animations 166 */ 167 private long mChangingAppearingStagger = 0; 168 private long mChangingDisappearingStagger = 0; 169 170 /** 171 * The default interpolators used for the animations 172 */ 173 private TimeInterpolator mAppearingInterpolator = new AccelerateDecelerateInterpolator(); 174 private TimeInterpolator mDisappearingInterpolator = new AccelerateDecelerateInterpolator(); 175 private TimeInterpolator mChangingAppearingInterpolator = new DecelerateInterpolator(); 176 private TimeInterpolator mChangingDisappearingInterpolator = new DecelerateInterpolator(); 177 178 /** 179 * These hashmaps are used to store the animations that are currently running as part of 180 * the transition. The reason for this is that a further layout event should cause 181 * existing animations to stop where they are prior to starting new animations. So 182 * we cache all of the current animations in this map for possible cancellation on 183 * another layout event. LinkedHashMaps are used to preserve the order in which animations 184 * are inserted, so that we process events (such as setting up start values) in the same order. 185 */ 186 private final HashMap<View, Animator> pendingAnimations = 187 new HashMap<View, Animator>(); 188 private final LinkedHashMap<View, Animator> currentChangingAnimations = 189 new LinkedHashMap<View, Animator>(); 190 private final LinkedHashMap<View, Animator> currentAppearingAnimations = 191 new LinkedHashMap<View, Animator>(); 192 private final LinkedHashMap<View, Animator> currentDisappearingAnimations = 193 new LinkedHashMap<View, Animator>(); 194 195 /** 196 * This hashmap is used to track the listeners that have been added to the children of 197 * a container. When a layout change occurs, an animation is created for each View, so that 198 * the pre-layout values can be cached in that animation. Then a listener is added to the 199 * view to see whether the layout changes the bounds of that view. If so, the animation 200 * is set with the final values and then run. If not, the animation is not started. When 201 * the process of setting up and running all appropriate animations is done, we need to 202 * remove these listeners and clear out the map. 203 */ 204 private final HashMap<View, View.OnLayoutChangeListener> layoutChangeListenerMap = 205 new HashMap<View, View.OnLayoutChangeListener>(); 206 207 /** 208 * Used to track the current delay being assigned to successive animations as they are 209 * started. This value is incremented for each new animation, then zeroed before the next 210 * transition begins. 211 */ 212 private long staggerDelay; 213 214 /** 215 * The set of listeners that should be notified when APPEARING/DISAPPEARING transitions 216 * start and end. 217 */ 218 private ArrayList<TransitionListener> mListeners; 219 220 /** 221 * Controls whether changing animations automatically animate the parent hierarchy as well. 222 * This behavior prevents artifacts when wrap_content layouts snap to the end state as the 223 * transition begins, causing visual glitches and clipping. 224 * Default value is true. 225 */ 226 private boolean mAnimateParentHierarchy = true; 227 228 229 /** 230 * Constructs a LayoutTransition object. By default, the object will listen to layout 231 * events on any ViewGroup that it is set on and will run default animations for each 232 * type of layout event. 233 */ LayoutTransition()234 public LayoutTransition() { 235 if (defaultChangeIn == null) { 236 // "left" is just a placeholder; we'll put real properties/values in when needed 237 PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1); 238 PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1); 239 PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1); 240 PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1); 241 PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1); 242 PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1); 243 defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null, 244 pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY); 245 defaultChangeIn.setDuration(DEFAULT_DURATION); 246 defaultChangeIn.setStartDelay(mChangingAppearingDelay); 247 defaultChangeIn.setInterpolator(mChangingAppearingInterpolator); 248 defaultChangeOut = defaultChangeIn.clone(); 249 defaultChangeOut.setStartDelay(mChangingDisappearingDelay); 250 defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator); 251 252 defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f); 253 defaultFadeIn.setDuration(DEFAULT_DURATION); 254 defaultFadeIn.setStartDelay(mAppearingDelay); 255 defaultFadeIn.setInterpolator(mAppearingInterpolator); 256 defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f); 257 defaultFadeOut.setDuration(DEFAULT_DURATION); 258 defaultFadeOut.setStartDelay(mDisappearingDelay); 259 defaultFadeOut.setInterpolator(mDisappearingInterpolator); 260 } 261 mChangingAppearingAnim = defaultChangeIn; 262 mChangingDisappearingAnim = defaultChangeOut; 263 mAppearingAnim = defaultFadeIn; 264 mDisappearingAnim = defaultFadeOut; 265 } 266 267 /** 268 * Sets the duration to be used by all animations of this transition object. If you want to 269 * set the duration of just one of the animations in particular, use the 270 * {@link #setDuration(int, long)} method. 271 * 272 * @param duration The length of time, in milliseconds, that the transition animations 273 * should last. 274 */ setDuration(long duration)275 public void setDuration(long duration) { 276 mChangingAppearingDuration = duration; 277 mChangingDisappearingDuration = duration; 278 mAppearingDuration = duration; 279 mDisappearingDuration = duration; 280 } 281 282 /** 283 * Sets the start delay on one of the animation objects used by this transition. The 284 * <code>transitionType</code> parameter determines the animation whose start delay 285 * is being set. 286 * 287 * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 288 * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start 289 * delay is being set. 290 * @param delay The length of time, in milliseconds, to delay before starting the animation. 291 * @see Animator#setStartDelay(long) 292 */ setStartDelay(int transitionType, long delay)293 public void setStartDelay(int transitionType, long delay) { 294 switch (transitionType) { 295 case CHANGE_APPEARING: 296 mChangingAppearingDelay = delay; 297 break; 298 case CHANGE_DISAPPEARING: 299 mChangingDisappearingDelay = delay; 300 break; 301 case APPEARING: 302 mAppearingDelay = delay; 303 break; 304 case DISAPPEARING: 305 mDisappearingDelay = delay; 306 break; 307 } 308 } 309 310 /** 311 * Gets the start delay on one of the animation objects used by this transition. The 312 * <code>transitionType</code> parameter determines the animation whose start delay 313 * is returned. 314 * 315 * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 316 * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start 317 * delay is returned. 318 * @return long The start delay of the specified animation. 319 * @see Animator#getStartDelay() 320 */ getStartDelay(int transitionType)321 public long getStartDelay(int transitionType) { 322 switch (transitionType) { 323 case CHANGE_APPEARING: 324 return mChangingAppearingDuration; 325 case CHANGE_DISAPPEARING: 326 return mChangingDisappearingDuration; 327 case APPEARING: 328 return mAppearingDuration; 329 case DISAPPEARING: 330 return mDisappearingDuration; 331 } 332 // shouldn't reach here 333 return 0; 334 } 335 336 /** 337 * Sets the duration on one of the animation objects used by this transition. The 338 * <code>transitionType</code> parameter determines the animation whose duration 339 * is being set. 340 * 341 * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 342 * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose 343 * duration is being set. 344 * @param duration The length of time, in milliseconds, that the specified animation should run. 345 * @see Animator#setDuration(long) 346 */ setDuration(int transitionType, long duration)347 public void setDuration(int transitionType, long duration) { 348 switch (transitionType) { 349 case CHANGE_APPEARING: 350 mChangingAppearingDuration = duration; 351 break; 352 case CHANGE_DISAPPEARING: 353 mChangingDisappearingDuration = duration; 354 break; 355 case APPEARING: 356 mAppearingDuration = duration; 357 break; 358 case DISAPPEARING: 359 mDisappearingDuration = duration; 360 break; 361 } 362 } 363 364 /** 365 * Gets the duration on one of the animation objects used by this transition. The 366 * <code>transitionType</code> parameter determines the animation whose duration 367 * is returned. 368 * 369 * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 370 * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose 371 * duration is returned. 372 * @return long The duration of the specified animation. 373 * @see Animator#getDuration() 374 */ getDuration(int transitionType)375 public long getDuration(int transitionType) { 376 switch (transitionType) { 377 case CHANGE_APPEARING: 378 return mChangingAppearingDuration; 379 case CHANGE_DISAPPEARING: 380 return mChangingDisappearingDuration; 381 case APPEARING: 382 return mAppearingDuration; 383 case DISAPPEARING: 384 return mDisappearingDuration; 385 } 386 // shouldn't reach here 387 return 0; 388 } 389 390 /** 391 * Sets the length of time to delay between starting each animation during one of the 392 * CHANGE animations. 393 * 394 * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}. 395 * @param duration The length of time, in milliseconds, to delay before launching the next 396 * animation in the sequence. 397 */ setStagger(int transitionType, long duration)398 public void setStagger(int transitionType, long duration) { 399 switch (transitionType) { 400 case CHANGE_APPEARING: 401 mChangingAppearingStagger = duration; 402 break; 403 case CHANGE_DISAPPEARING: 404 mChangingDisappearingStagger = duration; 405 break; 406 // noop other cases 407 } 408 } 409 410 /** 411 * Tets the length of time to delay between starting each animation during one of the 412 * CHANGE animations. 413 * 414 * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}. 415 * @return long The length of time, in milliseconds, to delay before launching the next 416 * animation in the sequence. 417 */ getStagger(int transitionType)418 public long getStagger(int transitionType) { 419 switch (transitionType) { 420 case CHANGE_APPEARING: 421 return mChangingAppearingStagger; 422 case CHANGE_DISAPPEARING: 423 return mChangingDisappearingStagger; 424 } 425 // shouldn't reach here 426 return 0; 427 } 428 429 /** 430 * Sets the interpolator on one of the animation objects used by this transition. The 431 * <code>transitionType</code> parameter determines the animation whose interpolator 432 * is being set. 433 * 434 * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 435 * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose 436 * duration is being set. 437 * @param interpolator The interpolator that the specified animation should use. 438 * @see Animator#setInterpolator(TimeInterpolator) 439 */ setInterpolator(int transitionType, TimeInterpolator interpolator)440 public void setInterpolator(int transitionType, TimeInterpolator interpolator) { 441 switch (transitionType) { 442 case CHANGE_APPEARING: 443 mChangingAppearingInterpolator = interpolator; 444 break; 445 case CHANGE_DISAPPEARING: 446 mChangingDisappearingInterpolator = interpolator; 447 break; 448 case APPEARING: 449 mAppearingInterpolator = interpolator; 450 break; 451 case DISAPPEARING: 452 mDisappearingInterpolator = interpolator; 453 break; 454 } 455 } 456 457 /** 458 * Gets the interpolator on one of the animation objects used by this transition. The 459 * <code>transitionType</code> parameter determines the animation whose interpolator 460 * is returned. 461 * 462 * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 463 * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose 464 * duration is being set. 465 * @return TimeInterpolator The interpolator that the specified animation uses. 466 * @see Animator#setInterpolator(TimeInterpolator) 467 */ getInterpolator(int transitionType)468 public TimeInterpolator getInterpolator(int transitionType) { 469 switch (transitionType) { 470 case CHANGE_APPEARING: 471 return mChangingAppearingInterpolator; 472 case CHANGE_DISAPPEARING: 473 return mChangingDisappearingInterpolator; 474 case APPEARING: 475 return mAppearingInterpolator; 476 case DISAPPEARING: 477 return mDisappearingInterpolator; 478 } 479 // shouldn't reach here 480 return null; 481 } 482 483 /** 484 * Sets the animation used during one of the transition types that may run. Any 485 * Animator object can be used, but to be most useful in the context of layout 486 * transitions, the animation should either be a ObjectAnimator or a AnimatorSet 487 * of animations including PropertyAnimators. Also, these ObjectAnimator objects 488 * should be able to get and set values on their target objects automatically. For 489 * example, a ObjectAnimator that animates the property "left" is able to set and get the 490 * <code>left</code> property from the View objects being animated by the layout 491 * transition. The transition works by setting target objects and properties 492 * dynamically, according to the pre- and post-layoout values of those objects, so 493 * having animations that can handle those properties appropriately will work best 494 * for custom animation. The dynamic setting of values is only the case for the 495 * CHANGE animations; the APPEARING and DISAPPEARING animations are simply run with 496 * the values they have. 497 * 498 * <p>It is also worth noting that any and all animations (and their underlying 499 * PropertyValuesHolder objects) will have their start and end values set according 500 * to the pre- and post-layout values. So, for example, a custom animation on "alpha" 501 * as the CHANGE_APPEARING animation will inherit the real value of alpha on the target 502 * object (presumably 1) as its starting and ending value when the animation begins. 503 * Animations which need to use values at the beginning and end that may not match the 504 * values queried when the transition begins may need to use a different mechanism 505 * than a standard ObjectAnimator object.</p> 506 * 507 * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 508 * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose 509 * duration is being set. 510 * @param animator The animation being assigned. A value of <code>null</code> means that no 511 * animation will be run for the specified transitionType. 512 */ setAnimator(int transitionType, Animator animator)513 public void setAnimator(int transitionType, Animator animator) { 514 switch (transitionType) { 515 case CHANGE_APPEARING: 516 mChangingAppearingAnim = animator; 517 break; 518 case CHANGE_DISAPPEARING: 519 mChangingDisappearingAnim = animator; 520 break; 521 case APPEARING: 522 mAppearingAnim = animator; 523 break; 524 case DISAPPEARING: 525 mDisappearingAnim = animator; 526 break; 527 } 528 } 529 530 /** 531 * Gets the animation used during one of the transition types that may run. 532 * 533 * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, 534 * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose 535 * duration is being set. 536 * @return Animator The animation being used for the given transition type. 537 * @see #setAnimator(int, Animator) 538 */ getAnimator(int transitionType)539 public Animator getAnimator(int transitionType) { 540 switch (transitionType) { 541 case CHANGE_APPEARING: 542 return mChangingAppearingAnim; 543 case CHANGE_DISAPPEARING: 544 return mChangingDisappearingAnim; 545 case APPEARING: 546 return mAppearingAnim; 547 case DISAPPEARING: 548 return mDisappearingAnim; 549 } 550 // shouldn't reach here 551 return null; 552 } 553 554 /** 555 * This function sets up animations on all of the views that change during layout. 556 * For every child in the parent, we create a change animation of the appropriate 557 * type (appearing or disappearing) and ask it to populate its start values from its 558 * target view. We add layout listeners to all child views and listen for changes. For 559 * those views that change, we populate the end values for those animations and start them. 560 * Animations are not run on unchanging views. 561 * 562 * @param parent The container which is undergoing an appearing or disappearing change. 563 * @param newView The view being added to or removed from the parent. 564 * @param changeReason A value of APPEARING or DISAPPEARING, indicating whether the 565 * transition is occuring because an item is being added to or removed from the parent. 566 */ runChangeTransition(final ViewGroup parent, View newView, final int changeReason)567 private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) { 568 569 Animator baseAnimator = (changeReason == APPEARING) ? 570 mChangingAppearingAnim : mChangingDisappearingAnim; 571 // If the animation is null, there's nothing to do 572 if (baseAnimator == null) { 573 return; 574 } 575 576 // reset the inter-animation delay, in case we use it later 577 staggerDelay = 0; 578 final long duration = (changeReason == APPEARING) ? 579 mChangingAppearingDuration : mChangingDisappearingDuration; 580 581 final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup 582 if (!observer.isAlive()) { 583 // If the observer's not in a good state, skip the transition 584 return; 585 } 586 int numChildren = parent.getChildCount(); 587 588 for (int i = 0; i < numChildren; ++i) { 589 final View child = parent.getChildAt(i); 590 591 // only animate the views not being added or removed 592 if (child != newView) { 593 setupChangeAnimation(parent, changeReason, baseAnimator, duration, child); 594 } 595 } 596 if (mAnimateParentHierarchy) { 597 Animator parentAnimator = (changeReason == APPEARING) ? 598 defaultChangeIn : defaultChangeOut; 599 ViewGroup tempParent = parent; 600 while (tempParent != null) { 601 ViewParent parentParent = tempParent.getParent(); 602 if (parentParent instanceof ViewGroup) { 603 setupChangeAnimation((ViewGroup)parentParent, changeReason, parentAnimator, 604 duration, tempParent); 605 tempParent = (ViewGroup) parentParent; 606 } else { 607 tempParent = null; 608 } 609 610 } 611 } 612 613 // This is the cleanup step. When we get this rendering event, we know that all of 614 // the appropriate animations have been set up and run. Now we can clear out the 615 // layout listeners. 616 observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 617 public boolean onPreDraw() { 618 parent.getViewTreeObserver().removeOnPreDrawListener(this); 619 int count = layoutChangeListenerMap.size(); 620 if (count > 0) { 621 Collection<View> views = layoutChangeListenerMap.keySet(); 622 for (View view : views) { 623 View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view); 624 view.removeOnLayoutChangeListener(listener); 625 } 626 } 627 layoutChangeListenerMap.clear(); 628 return true; 629 } 630 }); 631 } 632 633 /** 634 * This flag controls whether CHANGE_APPEARING or CHANGE_DISAPPEARING animations will 635 * cause the default changing animation to be run on the parent hierarchy as well. This allows 636 * containers of transitioning views to also transition, which may be necessary in situations 637 * where the containers bounds change between the before/after states and may clip their 638 * children during the transition animations. For example, layouts with wrap_content will 639 * adjust their bounds according to the dimensions of their children. 640 * 641 * <p>The default changing transitions animate the bounds and scroll positions of the 642 * target views. These are the animations that will run on the parent hierarchy, not 643 * the custom animations that happen to be set on the transition. This allows custom 644 * behavior for the children of the transitioning container, but uses standard behavior 645 * of resizing/rescrolling on any changing parents. 646 * 647 * @param animateParentHierarchy A boolean value indicating whether the parents of 648 * transitioning views should also be animated during the transition. Default value is true. 649 */ setAnimateParentHierarchy(boolean animateParentHierarchy)650 public void setAnimateParentHierarchy(boolean animateParentHierarchy) { 651 mAnimateParentHierarchy = animateParentHierarchy; 652 } 653 654 /** 655 * Utility function called by runChangingTransition for both the children and the parent 656 * hierarchy. 657 */ setupChangeAnimation(final ViewGroup parent, final int changeReason, Animator baseAnimator, final long duration, final View child)658 private void setupChangeAnimation(final ViewGroup parent, final int changeReason, 659 Animator baseAnimator, final long duration, final View child) { 660 // Make a copy of the appropriate animation 661 final Animator anim = baseAnimator.clone(); 662 663 // Set the target object for the animation 664 anim.setTarget(child); 665 666 // A ObjectAnimator (or AnimatorSet of them) can extract start values from 667 // its target object 668 anim.setupStartValues(); 669 670 // If there's an animation running on this view already, cancel it 671 Animator currentAnimation = pendingAnimations.get(child); 672 if (currentAnimation != null) { 673 currentAnimation.cancel(); 674 pendingAnimations.remove(child); 675 } 676 // Cache the animation in case we need to cancel it later 677 pendingAnimations.put(child, anim); 678 679 // For the animations which don't get started, we have to have a means of 680 // removing them from the cache, lest we leak them and their target objects. 681 // We run an animator for the default duration+100 (an arbitrary time, but one 682 // which should far surpass the delay between setting them up here and 683 // handling layout events which start them. 684 ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f). 685 setDuration(duration + 100); 686 pendingAnimRemover.addListener(new AnimatorListenerAdapter() { 687 @Override 688 public void onAnimationEnd(Animator animation) { 689 pendingAnimations.remove(child); 690 } 691 }); 692 pendingAnimRemover.start(); 693 694 // Add a listener to track layout changes on this view. If we don't get a callback, 695 // then there's nothing to animate. 696 final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() { 697 public void onLayoutChange(View v, int left, int top, int right, int bottom, 698 int oldLeft, int oldTop, int oldRight, int oldBottom) { 699 700 // Tell the animation to extract end values from the changed object 701 anim.setupEndValues(); 702 if (anim instanceof ValueAnimator) { 703 boolean valuesDiffer = false; 704 ValueAnimator valueAnim = (ValueAnimator)anim; 705 PropertyValuesHolder[] oldValues = valueAnim.getValues(); 706 for (int i = 0; i < oldValues.length; ++i) { 707 PropertyValuesHolder pvh = oldValues[i]; 708 KeyframeSet keyframeSet = pvh.mKeyframeSet; 709 if (keyframeSet.mFirstKeyframe == null || 710 keyframeSet.mLastKeyframe == null || 711 !keyframeSet.mFirstKeyframe.getValue().equals( 712 keyframeSet.mLastKeyframe.getValue())) { 713 valuesDiffer = true; 714 } 715 } 716 if (!valuesDiffer) { 717 return; 718 } 719 } 720 721 long startDelay; 722 if (changeReason == APPEARING) { 723 startDelay = mChangingAppearingDelay + staggerDelay; 724 staggerDelay += mChangingAppearingStagger; 725 } else { 726 startDelay = mChangingDisappearingDelay + staggerDelay; 727 staggerDelay += mChangingDisappearingStagger; 728 } 729 anim.setStartDelay(startDelay); 730 anim.setDuration(duration); 731 732 Animator prevAnimation = currentChangingAnimations.get(child); 733 if (prevAnimation != null) { 734 prevAnimation.cancel(); 735 } 736 Animator pendingAnimation = pendingAnimations.get(child); 737 if (pendingAnimation != null) { 738 pendingAnimations.remove(child); 739 } 740 // Cache the animation in case we need to cancel it later 741 currentChangingAnimations.put(child, anim); 742 743 parent.requestTransitionStart(LayoutTransition.this); 744 745 // this only removes listeners whose views changed - must clear the 746 // other listeners later 747 child.removeOnLayoutChangeListener(this); 748 layoutChangeListenerMap.remove(child); 749 } 750 }; 751 // Remove the animation from the cache when it ends 752 anim.addListener(new AnimatorListenerAdapter() { 753 754 @Override 755 public void onAnimationStart(Animator animator) { 756 if (mListeners != null) { 757 for (TransitionListener listener : mListeners) { 758 listener.startTransition(LayoutTransition.this, parent, child, 759 changeReason == APPEARING ? 760 CHANGE_APPEARING : CHANGE_DISAPPEARING); 761 } 762 } 763 } 764 765 @Override 766 public void onAnimationCancel(Animator animator) { 767 child.removeOnLayoutChangeListener(listener); 768 layoutChangeListenerMap.remove(child); 769 } 770 771 @Override 772 public void onAnimationEnd(Animator animator) { 773 currentChangingAnimations.remove(child); 774 if (mListeners != null) { 775 for (TransitionListener listener : mListeners) { 776 listener.endTransition(LayoutTransition.this, parent, child, 777 changeReason == APPEARING ? 778 CHANGE_APPEARING : CHANGE_DISAPPEARING); 779 } 780 } 781 } 782 }); 783 784 child.addOnLayoutChangeListener(listener); 785 // cache the listener for later removal 786 layoutChangeListenerMap.put(child, listener); 787 } 788 789 /** 790 * Starts the animations set up for a CHANGING transition. We separate the setup of these 791 * animations from actually starting them, to avoid side-effects that starting the animations 792 * may have on the properties of the affected objects. After setup, we tell the affected parent 793 * that this transition should be started. The parent informs its ViewAncestor, which then 794 * starts the transition after the current layout/measurement phase, just prior to drawing 795 * the view hierarchy. 796 * 797 * @hide 798 */ startChangingAnimations()799 public void startChangingAnimations() { 800 LinkedHashMap<View, Animator> currentAnimCopy = 801 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 802 for (Animator anim : currentAnimCopy.values()) { 803 if (anim instanceof ObjectAnimator) { 804 ((ObjectAnimator) anim).setCurrentPlayTime(0); 805 } 806 anim.start(); 807 } 808 } 809 810 /** 811 * Ends the animations that are set up for a CHANGING transition. This is a variant of 812 * startChangingAnimations() which is called when the window the transition is playing in 813 * is not visible. We need to make sure the animations put their targets in their end states 814 * and that the transition finishes to remove any mid-process state (such as isRunning()). 815 * 816 * @hide 817 */ endChangingAnimations()818 public void endChangingAnimations() { 819 LinkedHashMap<View, Animator> currentAnimCopy = 820 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 821 for (Animator anim : currentAnimCopy.values()) { 822 anim.start(); 823 anim.end(); 824 } 825 } 826 827 /** 828 * Returns true if animations are running which animate layout-related properties. This 829 * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations 830 * are running, since these animations operate on layout-related properties. 831 * 832 * @return true if CHANGE_APPEARING or CHANGE_DISAPPEARING animations are currently 833 * running. 834 */ isChangingLayout()835 public boolean isChangingLayout() { 836 return (currentChangingAnimations.size() > 0); 837 } 838 839 /** 840 * Returns true if any of the animations in this transition are currently running. 841 * 842 * @return true if any animations in the transition are running. 843 */ isRunning()844 public boolean isRunning() { 845 return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 || 846 currentDisappearingAnimations.size() > 0); 847 } 848 849 /** 850 * Cancels the currently running transition. Note that we cancel() the changing animations 851 * but end() the visibility animations. This is because this method is currently called 852 * in the context of starting a new transition, so we want to move things from their mid- 853 * transition positions, but we want them to have their end-transition visibility. 854 * 855 * @hide 856 */ cancel()857 public void cancel() { 858 if (currentChangingAnimations.size() > 0) { 859 LinkedHashMap<View, Animator> currentAnimCopy = 860 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 861 for (Animator anim : currentAnimCopy.values()) { 862 anim.cancel(); 863 } 864 currentChangingAnimations.clear(); 865 } 866 if (currentAppearingAnimations.size() > 0) { 867 LinkedHashMap<View, Animator> currentAnimCopy = 868 (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone(); 869 for (Animator anim : currentAnimCopy.values()) { 870 anim.end(); 871 } 872 currentAppearingAnimations.clear(); 873 } 874 if (currentDisappearingAnimations.size() > 0) { 875 LinkedHashMap<View, Animator> currentAnimCopy = 876 (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone(); 877 for (Animator anim : currentAnimCopy.values()) { 878 anim.end(); 879 } 880 currentDisappearingAnimations.clear(); 881 } 882 } 883 884 /** 885 * Cancels the specified type of transition. Note that we cancel() the changing animations 886 * but end() the visibility animations. This is because this method is currently called 887 * in the context of starting a new transition, so we want to move things from their mid- 888 * transition positions, but we want them to have their end-transition visibility. 889 * 890 * @hide 891 */ cancel(int transitionType)892 public void cancel(int transitionType) { 893 switch (transitionType) { 894 case CHANGE_APPEARING: 895 case CHANGE_DISAPPEARING: 896 if (currentChangingAnimations.size() > 0) { 897 LinkedHashMap<View, Animator> currentAnimCopy = 898 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 899 for (Animator anim : currentAnimCopy.values()) { 900 anim.cancel(); 901 } 902 currentChangingAnimations.clear(); 903 } 904 break; 905 case APPEARING: 906 if (currentAppearingAnimations.size() > 0) { 907 LinkedHashMap<View, Animator> currentAnimCopy = 908 (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone(); 909 for (Animator anim : currentAnimCopy.values()) { 910 anim.end(); 911 } 912 currentAppearingAnimations.clear(); 913 } 914 break; 915 case DISAPPEARING: 916 if (currentDisappearingAnimations.size() > 0) { 917 LinkedHashMap<View, Animator> currentAnimCopy = 918 (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone(); 919 for (Animator anim : currentAnimCopy.values()) { 920 anim.end(); 921 } 922 currentDisappearingAnimations.clear(); 923 } 924 break; 925 } 926 } 927 928 /** 929 * This method runs the animation that makes an added item appear. 930 * 931 * @param parent The ViewGroup to which the View is being added. 932 * @param child The View being added to the ViewGroup. 933 */ runAppearingTransition(final ViewGroup parent, final View child)934 private void runAppearingTransition(final ViewGroup parent, final View child) { 935 Animator currentAnimation = currentDisappearingAnimations.get(child); 936 if (currentAnimation != null) { 937 currentAnimation.cancel(); 938 } 939 if (mAppearingAnim == null) { 940 if (mListeners != null) { 941 for (TransitionListener listener : mListeners) { 942 listener.endTransition(LayoutTransition.this, parent, child, APPEARING); 943 } 944 } 945 return; 946 } 947 Animator anim = mAppearingAnim.clone(); 948 anim.setTarget(child); 949 anim.setStartDelay(mAppearingDelay); 950 anim.setDuration(mAppearingDuration); 951 if (anim instanceof ObjectAnimator) { 952 ((ObjectAnimator) anim).setCurrentPlayTime(0); 953 } 954 if (mListeners != null) { 955 anim.addListener(new AnimatorListenerAdapter() { 956 @Override 957 public void onAnimationEnd(Animator anim) { 958 currentAppearingAnimations.remove(child); 959 for (TransitionListener listener : mListeners) { 960 listener.endTransition(LayoutTransition.this, parent, child, APPEARING); 961 } 962 } 963 }); 964 } 965 currentAppearingAnimations.put(child, anim); 966 anim.start(); 967 } 968 969 /** 970 * This method runs the animation that makes a removed item disappear. 971 * 972 * @param parent The ViewGroup from which the View is being removed. 973 * @param child The View being removed from the ViewGroup. 974 */ runDisappearingTransition(final ViewGroup parent, final View child)975 private void runDisappearingTransition(final ViewGroup parent, final View child) { 976 Animator currentAnimation = currentAppearingAnimations.get(child); 977 if (currentAnimation != null) { 978 currentAnimation.cancel(); 979 } 980 if (mDisappearingAnim == null) { 981 if (mListeners != null) { 982 for (TransitionListener listener : mListeners) { 983 listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); 984 } 985 } 986 return; 987 } 988 Animator anim = mDisappearingAnim.clone(); 989 anim.setStartDelay(mDisappearingDelay); 990 anim.setDuration(mDisappearingDuration); 991 anim.setTarget(child); 992 if (mListeners != null) { 993 anim.addListener(new AnimatorListenerAdapter() { 994 @Override 995 public void onAnimationEnd(Animator anim) { 996 currentDisappearingAnimations.remove(child); 997 for (TransitionListener listener : mListeners) { 998 listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); 999 } 1000 } 1001 }); 1002 } 1003 if (anim instanceof ObjectAnimator) { 1004 ((ObjectAnimator) anim).setCurrentPlayTime(0); 1005 } 1006 currentDisappearingAnimations.put(child, anim); 1007 anim.start(); 1008 } 1009 1010 /** 1011 * This method is called by ViewGroup when a child view is about to be added to the 1012 * container. This callback starts the process of a transition; we grab the starting 1013 * values, listen for changes to all of the children of the container, and start appropriate 1014 * animations. 1015 * 1016 * @param parent The ViewGroup to which the View is being added. 1017 * @param child The View being added to the ViewGroup. 1018 */ addChild(ViewGroup parent, View child)1019 public void addChild(ViewGroup parent, View child) { 1020 // Want disappearing animations to finish up before proceeding 1021 cancel(DISAPPEARING); 1022 // Also, cancel changing animations so that we start fresh ones from current locations 1023 cancel(CHANGE_APPEARING); 1024 if (mListeners != null) { 1025 for (TransitionListener listener : mListeners) { 1026 listener.startTransition(this, parent, child, APPEARING); 1027 } 1028 } 1029 runChangeTransition(parent, child, APPEARING); 1030 runAppearingTransition(parent, child); 1031 } 1032 1033 /** 1034 * This method is called by ViewGroup when a child view is about to be added to the 1035 * container. This callback starts the process of a transition; we grab the starting 1036 * values, listen for changes to all of the children of the container, and start appropriate 1037 * animations. 1038 * 1039 * @param parent The ViewGroup to which the View is being added. 1040 * @param child The View being added to the ViewGroup. 1041 */ showChild(ViewGroup parent, View child)1042 public void showChild(ViewGroup parent, View child) { 1043 addChild(parent, child); 1044 } 1045 1046 /** 1047 * This method is called by ViewGroup when a child view is about to be removed from the 1048 * container. This callback starts the process of a transition; we grab the starting 1049 * values, listen for changes to all of the children of the container, and start appropriate 1050 * animations. 1051 * 1052 * @param parent The ViewGroup from which the View is being removed. 1053 * @param child The View being removed from the ViewGroup. 1054 */ removeChild(ViewGroup parent, View child)1055 public void removeChild(ViewGroup parent, View child) { 1056 // Want appearing animations to finish up before proceeding 1057 cancel(APPEARING); 1058 // Also, cancel changing animations so that we start fresh ones from current locations 1059 cancel(CHANGE_DISAPPEARING); 1060 if (mListeners != null) { 1061 for (TransitionListener listener : mListeners) { 1062 listener.startTransition(this, parent, child, DISAPPEARING); 1063 } 1064 } 1065 runChangeTransition(parent, child, DISAPPEARING); 1066 runDisappearingTransition(parent, child); 1067 } 1068 1069 /** 1070 * This method is called by ViewGroup when a child view is about to be removed from the 1071 * container. This callback starts the process of a transition; we grab the starting 1072 * values, listen for changes to all of the children of the container, and start appropriate 1073 * animations. 1074 * 1075 * @param parent The ViewGroup from which the View is being removed. 1076 * @param child The View being removed from the ViewGroup. 1077 */ hideChild(ViewGroup parent, View child)1078 public void hideChild(ViewGroup parent, View child) { 1079 removeChild(parent, child); 1080 } 1081 1082 /** 1083 * Add a listener that will be called when the bounds of the view change due to 1084 * layout processing. 1085 * 1086 * @param listener The listener that will be called when layout bounds change. 1087 */ addTransitionListener(TransitionListener listener)1088 public void addTransitionListener(TransitionListener listener) { 1089 if (mListeners == null) { 1090 mListeners = new ArrayList<TransitionListener>(); 1091 } 1092 mListeners.add(listener); 1093 } 1094 1095 /** 1096 * Remove a listener for layout changes. 1097 * 1098 * @param listener The listener for layout bounds change. 1099 */ removeTransitionListener(TransitionListener listener)1100 public void removeTransitionListener(TransitionListener listener) { 1101 if (mListeners == null) { 1102 return; 1103 } 1104 mListeners.remove(listener); 1105 } 1106 1107 /** 1108 * Gets the current list of listeners for layout changes. 1109 * @return 1110 */ getTransitionListeners()1111 public List<TransitionListener> getTransitionListeners() { 1112 return mListeners; 1113 } 1114 1115 /** 1116 * This interface is used for listening to starting and ending events for transitions. 1117 */ 1118 public interface TransitionListener { 1119 1120 /** 1121 * This event is sent to listeners when any type of transition animation begins. 1122 * 1123 * @param transition The LayoutTransition sending out the event. 1124 * @param container The ViewGroup on which the transition is playing. 1125 * @param view The View object being affected by the transition animation. 1126 * @param transitionType The type of transition that is beginning, 1127 * {@link android.animation.LayoutTransition#APPEARING}, 1128 * {@link android.animation.LayoutTransition#DISAPPEARING}, 1129 * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or 1130 * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}. 1131 */ startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)1132 public void startTransition(LayoutTransition transition, ViewGroup container, 1133 View view, int transitionType); 1134 1135 /** 1136 * This event is sent to listeners when any type of transition animation ends. 1137 * 1138 * @param transition The LayoutTransition sending out the event. 1139 * @param container The ViewGroup on which the transition is playing. 1140 * @param view The View object being affected by the transition animation. 1141 * @param transitionType The type of transition that is ending, 1142 * {@link android.animation.LayoutTransition#APPEARING}, 1143 * {@link android.animation.LayoutTransition#DISAPPEARING}, 1144 * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or 1145 * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}. 1146 */ endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)1147 public void endTransition(LayoutTransition transition, ViewGroup container, 1148 View view, int transitionType); 1149 } 1150 1151 } 1152