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 // 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 1147 public void endTransition(LayoutTransition transition, ViewGroup container, 1148 View view, int transitionType); 1149 } 1150 1151 } 1152