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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 658 private void setupChangeAnimation(final ViewGroup parent, final int changeReason, 659 Animator baseAnimator, final long duration, final View child) { 660 661 // If we already have a listener for this child, then we've already set up the 662 // changing animation we need. Multiple calls for a child may occur when several 663 // add/remove operations are run at once on a container; each one will trigger 664 // changes for the existing children in the container. 665 if (layoutChangeListenerMap.get(child) != null) { 666 return; 667 } 668 669 // Make a copy of the appropriate animation 670 final Animator anim = baseAnimator.clone(); 671 672 // Set the target object for the animation 673 anim.setTarget(child); 674 675 // A ObjectAnimator (or AnimatorSet of them) can extract start values from 676 // its target object 677 anim.setupStartValues(); 678 679 // If there's an animation running on this view already, cancel it 680 Animator currentAnimation = pendingAnimations.get(child); 681 if (currentAnimation != null) { 682 currentAnimation.cancel(); 683 pendingAnimations.remove(child); 684 } 685 // Cache the animation in case we need to cancel it later 686 pendingAnimations.put(child, anim); 687 688 // For the animations which don't get started, we have to have a means of 689 // removing them from the cache, lest we leak them and their target objects. 690 // We run an animator for the default duration+100 (an arbitrary time, but one 691 // which should far surpass the delay between setting them up here and 692 // handling layout events which start them. 693 ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f). 694 setDuration(duration + 100); 695 pendingAnimRemover.addListener(new AnimatorListenerAdapter() { 696 @Override 697 public void onAnimationEnd(Animator animation) { 698 pendingAnimations.remove(child); 699 } 700 }); 701 pendingAnimRemover.start(); 702 703 // Add a listener to track layout changes on this view. If we don't get a callback, 704 // then there's nothing to animate. 705 final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() { 706 public void onLayoutChange(View v, int left, int top, int right, int bottom, 707 int oldLeft, int oldTop, int oldRight, int oldBottom) { 708 709 // Tell the animation to extract end values from the changed object 710 anim.setupEndValues(); 711 if (anim instanceof ValueAnimator) { 712 boolean valuesDiffer = false; 713 ValueAnimator valueAnim = (ValueAnimator)anim; 714 PropertyValuesHolder[] oldValues = valueAnim.getValues(); 715 for (int i = 0; i < oldValues.length; ++i) { 716 PropertyValuesHolder pvh = oldValues[i]; 717 KeyframeSet keyframeSet = pvh.mKeyframeSet; 718 if (keyframeSet.mFirstKeyframe == null || 719 keyframeSet.mLastKeyframe == null || 720 !keyframeSet.mFirstKeyframe.getValue().equals( 721 keyframeSet.mLastKeyframe.getValue())) { 722 valuesDiffer = true; 723 } 724 } 725 if (!valuesDiffer) { 726 return; 727 } 728 } 729 730 long startDelay; 731 if (changeReason == APPEARING) { 732 startDelay = mChangingAppearingDelay + staggerDelay; 733 staggerDelay += mChangingAppearingStagger; 734 } else { 735 startDelay = mChangingDisappearingDelay + staggerDelay; 736 staggerDelay += mChangingDisappearingStagger; 737 } 738 anim.setStartDelay(startDelay); 739 anim.setDuration(duration); 740 741 Animator prevAnimation = currentChangingAnimations.get(child); 742 if (prevAnimation != null) { 743 prevAnimation.cancel(); 744 } 745 Animator pendingAnimation = pendingAnimations.get(child); 746 if (pendingAnimation != null) { 747 pendingAnimations.remove(child); 748 } 749 // Cache the animation in case we need to cancel it later 750 currentChangingAnimations.put(child, anim); 751 752 parent.requestTransitionStart(LayoutTransition.this); 753 754 // this only removes listeners whose views changed - must clear the 755 // other listeners later 756 child.removeOnLayoutChangeListener(this); 757 layoutChangeListenerMap.remove(child); 758 } 759 }; 760 // Remove the animation from the cache when it ends 761 anim.addListener(new AnimatorListenerAdapter() { 762 763 @Override 764 public void onAnimationStart(Animator animator) { 765 if (mListeners != null) { 766 for (TransitionListener listener : mListeners) { 767 listener.startTransition(LayoutTransition.this, parent, child, 768 changeReason == APPEARING ? 769 CHANGE_APPEARING : CHANGE_DISAPPEARING); 770 } 771 } 772 } 773 774 @Override 775 public void onAnimationCancel(Animator animator) { 776 child.removeOnLayoutChangeListener(listener); 777 layoutChangeListenerMap.remove(child); 778 } 779 780 @Override 781 public void onAnimationEnd(Animator animator) { 782 currentChangingAnimations.remove(child); 783 if (mListeners != null) { 784 for (TransitionListener listener : mListeners) { 785 listener.endTransition(LayoutTransition.this, parent, child, 786 changeReason == APPEARING ? 787 CHANGE_APPEARING : CHANGE_DISAPPEARING); 788 } 789 } 790 } 791 }); 792 793 child.addOnLayoutChangeListener(listener); 794 // cache the listener for later removal 795 layoutChangeListenerMap.put(child, listener); 796 } 797 798 /** 799 * Starts the animations set up for a CHANGING transition. We separate the setup of these 800 * animations from actually starting them, to avoid side-effects that starting the animations 801 * may have on the properties of the affected objects. After setup, we tell the affected parent 802 * that this transition should be started. The parent informs its ViewAncestor, which then 803 * starts the transition after the current layout/measurement phase, just prior to drawing 804 * the view hierarchy. 805 * 806 * @hide 807 */ 808 public void startChangingAnimations() { 809 LinkedHashMap<View, Animator> currentAnimCopy = 810 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 811 for (Animator anim : currentAnimCopy.values()) { 812 if (anim instanceof ObjectAnimator) { 813 ((ObjectAnimator) anim).setCurrentPlayTime(0); 814 } 815 anim.start(); 816 } 817 } 818 819 /** 820 * Ends the animations that are set up for a CHANGING transition. This is a variant of 821 * startChangingAnimations() which is called when the window the transition is playing in 822 * is not visible. We need to make sure the animations put their targets in their end states 823 * and that the transition finishes to remove any mid-process state (such as isRunning()). 824 * 825 * @hide 826 */ 827 public void endChangingAnimations() { 828 LinkedHashMap<View, Animator> currentAnimCopy = 829 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 830 for (Animator anim : currentAnimCopy.values()) { 831 anim.start(); 832 anim.end(); 833 } 834 } 835 836 /** 837 * Returns true if animations are running which animate layout-related properties. This 838 * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations 839 * are running, since these animations operate on layout-related properties. 840 * 841 * @return true if CHANGE_APPEARING or CHANGE_DISAPPEARING animations are currently 842 * running. 843 */ 844 public boolean isChangingLayout() { 845 return (currentChangingAnimations.size() > 0); 846 } 847 848 /** 849 * Returns true if any of the animations in this transition are currently running. 850 * 851 * @return true if any animations in the transition are running. 852 */ 853 public boolean isRunning() { 854 return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 || 855 currentDisappearingAnimations.size() > 0); 856 } 857 858 /** 859 * Cancels the currently running transition. Note that we cancel() the changing animations 860 * but end() the visibility animations. This is because this method is currently called 861 * in the context of starting a new transition, so we want to move things from their mid- 862 * transition positions, but we want them to have their end-transition visibility. 863 * 864 * @hide 865 */ 866 public void cancel() { 867 if (currentChangingAnimations.size() > 0) { 868 LinkedHashMap<View, Animator> currentAnimCopy = 869 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 870 for (Animator anim : currentAnimCopy.values()) { 871 anim.cancel(); 872 } 873 currentChangingAnimations.clear(); 874 } 875 if (currentAppearingAnimations.size() > 0) { 876 LinkedHashMap<View, Animator> currentAnimCopy = 877 (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone(); 878 for (Animator anim : currentAnimCopy.values()) { 879 anim.end(); 880 } 881 currentAppearingAnimations.clear(); 882 } 883 if (currentDisappearingAnimations.size() > 0) { 884 LinkedHashMap<View, Animator> currentAnimCopy = 885 (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone(); 886 for (Animator anim : currentAnimCopy.values()) { 887 anim.end(); 888 } 889 currentDisappearingAnimations.clear(); 890 } 891 } 892 893 /** 894 * Cancels the specified type of transition. Note that we cancel() the changing animations 895 * but end() the visibility animations. This is because this method is currently called 896 * in the context of starting a new transition, so we want to move things from their mid- 897 * transition positions, but we want them to have their end-transition visibility. 898 * 899 * @hide 900 */ 901 public void cancel(int transitionType) { 902 switch (transitionType) { 903 case CHANGE_APPEARING: 904 case CHANGE_DISAPPEARING: 905 if (currentChangingAnimations.size() > 0) { 906 LinkedHashMap<View, Animator> currentAnimCopy = 907 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); 908 for (Animator anim : currentAnimCopy.values()) { 909 anim.cancel(); 910 } 911 currentChangingAnimations.clear(); 912 } 913 break; 914 case APPEARING: 915 if (currentAppearingAnimations.size() > 0) { 916 LinkedHashMap<View, Animator> currentAnimCopy = 917 (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone(); 918 for (Animator anim : currentAnimCopy.values()) { 919 anim.end(); 920 } 921 currentAppearingAnimations.clear(); 922 } 923 break; 924 case DISAPPEARING: 925 if (currentDisappearingAnimations.size() > 0) { 926 LinkedHashMap<View, Animator> currentAnimCopy = 927 (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone(); 928 for (Animator anim : currentAnimCopy.values()) { 929 anim.end(); 930 } 931 currentDisappearingAnimations.clear(); 932 } 933 break; 934 } 935 } 936 937 /** 938 * This method runs the animation that makes an added item appear. 939 * 940 * @param parent The ViewGroup to which the View is being added. 941 * @param child The View being added to the ViewGroup. 942 */ 943 private void runAppearingTransition(final ViewGroup parent, final View child) { 944 Animator currentAnimation = currentDisappearingAnimations.get(child); 945 if (currentAnimation != null) { 946 currentAnimation.cancel(); 947 } 948 if (mAppearingAnim == null) { 949 if (mListeners != null) { 950 for (TransitionListener listener : mListeners) { 951 listener.endTransition(LayoutTransition.this, parent, child, APPEARING); 952 } 953 } 954 return; 955 } 956 Animator anim = mAppearingAnim.clone(); 957 anim.setTarget(child); 958 anim.setStartDelay(mAppearingDelay); 959 anim.setDuration(mAppearingDuration); 960 if (anim instanceof ObjectAnimator) { 961 ((ObjectAnimator) anim).setCurrentPlayTime(0); 962 } 963 if (mListeners != null) { 964 anim.addListener(new AnimatorListenerAdapter() { 965 @Override 966 public void onAnimationEnd(Animator anim) { 967 currentAppearingAnimations.remove(child); 968 for (TransitionListener listener : mListeners) { 969 listener.endTransition(LayoutTransition.this, parent, child, APPEARING); 970 } 971 } 972 }); 973 } 974 currentAppearingAnimations.put(child, anim); 975 anim.start(); 976 } 977 978 /** 979 * This method runs the animation that makes a removed item disappear. 980 * 981 * @param parent The ViewGroup from which the View is being removed. 982 * @param child The View being removed from the ViewGroup. 983 */ 984 private void runDisappearingTransition(final ViewGroup parent, final View child) { 985 Animator currentAnimation = currentAppearingAnimations.get(child); 986 if (currentAnimation != null) { 987 currentAnimation.cancel(); 988 } 989 if (mDisappearingAnim == null) { 990 if (mListeners != null) { 991 for (TransitionListener listener : mListeners) { 992 listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); 993 } 994 } 995 return; 996 } 997 Animator anim = mDisappearingAnim.clone(); 998 anim.setStartDelay(mDisappearingDelay); 999 anim.setDuration(mDisappearingDuration); 1000 anim.setTarget(child); 1001 if (mListeners != null) { 1002 anim.addListener(new AnimatorListenerAdapter() { 1003 @Override 1004 public void onAnimationEnd(Animator anim) { 1005 currentDisappearingAnimations.remove(child); 1006 for (TransitionListener listener : mListeners) { 1007 listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); 1008 } 1009 } 1010 }); 1011 } 1012 if (anim instanceof ObjectAnimator) { 1013 ((ObjectAnimator) anim).setCurrentPlayTime(0); 1014 } 1015 currentDisappearingAnimations.put(child, anim); 1016 anim.start(); 1017 } 1018 1019 /** 1020 * This method is called by ViewGroup when a child view is about to be added to the 1021 * container. This callback starts the process of a transition; we grab the starting 1022 * values, listen for changes to all of the children of the container, and start appropriate 1023 * animations. 1024 * 1025 * @param parent The ViewGroup to which the View is being added. 1026 * @param child The View being added to the ViewGroup. 1027 */ 1028 public void addChild(ViewGroup parent, View child) { 1029 // Want disappearing animations to finish up before proceeding 1030 cancel(DISAPPEARING); 1031 // Also, cancel changing animations so that we start fresh ones from current locations 1032 cancel(CHANGE_APPEARING); 1033 if (mListeners != null) { 1034 for (TransitionListener listener : mListeners) { 1035 listener.startTransition(this, parent, child, APPEARING); 1036 } 1037 } 1038 runChangeTransition(parent, child, APPEARING); 1039 runAppearingTransition(parent, child); 1040 } 1041 1042 /** 1043 * This method is called by ViewGroup when a child view is about to be added to the 1044 * container. This callback starts the process of a transition; we grab the starting 1045 * values, listen for changes to all of the children of the container, and start appropriate 1046 * animations. 1047 * 1048 * @param parent The ViewGroup to which the View is being added. 1049 * @param child The View being added to the ViewGroup. 1050 */ 1051 public void showChild(ViewGroup parent, View child) { 1052 addChild(parent, child); 1053 } 1054 1055 /** 1056 * This method is called by ViewGroup when a child view is about to be removed from the 1057 * container. This callback starts the process of a transition; we grab the starting 1058 * values, listen for changes to all of the children of the container, and start appropriate 1059 * animations. 1060 * 1061 * @param parent The ViewGroup from which the View is being removed. 1062 * @param child The View being removed from the ViewGroup. 1063 */ 1064 public void removeChild(ViewGroup parent, View child) { 1065 // Want appearing animations to finish up before proceeding 1066 cancel(APPEARING); 1067 // Also, cancel changing animations so that we start fresh ones from current locations 1068 cancel(CHANGE_DISAPPEARING); 1069 if (mListeners != null) { 1070 for (TransitionListener listener : mListeners) { 1071 listener.startTransition(this, parent, child, DISAPPEARING); 1072 } 1073 } 1074 runChangeTransition(parent, child, DISAPPEARING); 1075 runDisappearingTransition(parent, child); 1076 } 1077 1078 /** 1079 * This method is called by ViewGroup when a child view is about to be removed from the 1080 * container. This callback starts the process of a transition; we grab the starting 1081 * values, listen for changes to all of the children of the container, and start appropriate 1082 * animations. 1083 * 1084 * @param parent The ViewGroup from which the View is being removed. 1085 * @param child The View being removed from the ViewGroup. 1086 */ 1087 public void hideChild(ViewGroup parent, View child) { 1088 removeChild(parent, child); 1089 } 1090 1091 /** 1092 * Add a listener that will be called when the bounds of the view change due to 1093 * layout processing. 1094 * 1095 * @param listener The listener that will be called when layout bounds change. 1096 */ 1097 public void addTransitionListener(TransitionListener listener) { 1098 if (mListeners == null) { 1099 mListeners = new ArrayList<TransitionListener>(); 1100 } 1101 mListeners.add(listener); 1102 } 1103 1104 /** 1105 * Remove a listener for layout changes. 1106 * 1107 * @param listener The listener for layout bounds change. 1108 */ 1109 public void removeTransitionListener(TransitionListener listener) { 1110 if (mListeners == null) { 1111 return; 1112 } 1113 mListeners.remove(listener); 1114 } 1115 1116 /** 1117 * Gets the current list of listeners for layout changes. 1118 * @return 1119 */ 1120 public List<TransitionListener> getTransitionListeners() { 1121 return mListeners; 1122 } 1123 1124 /** 1125 * This interface is used for listening to starting and ending events for transitions. 1126 */ 1127 public interface TransitionListener { 1128 1129 /** 1130 * This event is sent to listeners when any type of transition animation begins. 1131 * 1132 * @param transition The LayoutTransition sending out the event. 1133 * @param container The ViewGroup on which the transition is playing. 1134 * @param view The View object being affected by the transition animation. 1135 * @param transitionType The type of transition that is beginning, 1136 * {@link android.animation.LayoutTransition#APPEARING}, 1137 * {@link android.animation.LayoutTransition#DISAPPEARING}, 1138 * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or 1139 * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}. 1140 */ 1141 public void startTransition(LayoutTransition transition, ViewGroup container, 1142 View view, int transitionType); 1143 1144 /** 1145 * This event is sent to listeners when any type of transition animation ends. 1146 * 1147 * @param transition The LayoutTransition sending out the event. 1148 * @param container The ViewGroup on which the transition is playing. 1149 * @param view The View object being affected by the transition animation. 1150 * @param transitionType The type of transition that is ending, 1151 * {@link android.animation.LayoutTransition#APPEARING}, 1152 * {@link android.animation.LayoutTransition#DISAPPEARING}, 1153 * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or 1154 * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}. 1155 */ 1156 public void endTransition(LayoutTransition transition, ViewGroup container, 1157 View view, int transitionType); 1158 } 1159 1160 } 1161