content: /*
   * Copyright (C) 2010 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.animation;
  
  import android.annotation.UnsupportedAppUsage;
  import android.os.Build;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.ViewParent;
  import android.view.ViewTreeObserver;
  import android.view.animation.AccelerateDecelerateInterpolator;
  import android.view.animation.DecelerateInterpolator;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  
  /**
   * This class enables automatic animations on layout changes in ViewGroup objects. To enable
   * transitions for a layout container, create a LayoutTransition object and set it on any
   * ViewGroup by calling {@link ViewGroup#setLayoutTransition(LayoutTransition)}. This will cause
   * default animations to run whenever items are added to or removed from that container. To specify
   * custom animations, use the {@link LayoutTransition#setAnimator(int, Animator)
   * setAnimator()} method.
   *
   * <p>One of the core concepts of these transition animations is that there are two types of
   * changes that cause the transition and four different animations that run because of
   * those changes. The changes that trigger the transition are items being added to a container
   * (referred to as an "appearing" transition) or removed from a container (also known as
   * "disappearing"). Setting the visibility of views (between GONE and VISIBLE) will trigger
   * the same add/remove logic. The animations that run due to those events are one that animates
   * items being added, one that animates items being removed, and two that animate the other
   * items in the container that change due to the add/remove occurrence. Users of
   * the transition may want different animations for the changing items depending on whether
   * they are changing due to an appearing or disappearing event, so there is one animation for
   * each of these variations of the changing event. Most of the API of this class is concerned
   * with setting up the basic properties of the animations used in these four situations,
   * or with setting up custom animations for any or all of the four.</p>
   *
   * <p>By default, the DISAPPEARING animation begins immediately, as does the CHANGE_APPEARING
   * animation. The other animations begin after a delay that is set to the default duration
   * of the animations. This behavior facilitates a sequence of animations in transitions as
   * follows: when an item is being added to a layout, the other children of that container will
   * move first (thus creating space for the new item), then the appearing animation will run to
   * animate the item being added. Conversely, when an item is removed from a container, the
   * animation to remove it will run first, then the animations of the other children in the
   * layout will run (closing the gap created in the layout when the item was removed). If this
   * default choreography behavior is not desired, the {@link #setDuration(int, long)} and
   * {@link #setStartDelay(int, long)} of any or all of the animations can be changed as
   * appropriate. Keep in mind, however, that if you start an APPEARING animation before a
   * DISAPPEARING animation is completed, the DISAPPEARING animation stops, and any effects from
   * the DISAPPEARING animation are reverted. If you instead start a DISAPPEARING animation
   * before an APPEARING animation is completed, a similar set of effects occurs for the
   * APPEARING animation.</p>
   *
   * <p>The animations specified for the transition, both the defaults and any custom animations
   * set on the transition object, are templates only. That is, these animations exist to hold the
   * basic animation properties, such as the duration, start delay, and properties being animated.
   * But the actual target object, as well as the start and end values for those properties, are
   * set automatically in the process of setting up the transition each time it runs. Each of the
   * animations is cloned from the original copy and the clone is then populated with the dynamic
   * values of the target being animated (such as one of the items in a layout container that is
   * moving as a result of the layout event) as well as the values that are changing (such as the
   * position and size of that object). The actual values that are pushed to each animation
   * depends on what properties are specified for the animation. For example, the default
   * CHANGE_APPEARING animation animates the <code>left</code>, <code>top</code>, <code>right</code>,
   * <code>bottom</code>, <code>scrollX</code>, and <code>scrollY</code> properties.
   * Values for these properties are updated with the pre- and post-layout
   * values when the transition begins. Custom animations will be similarly populated with
   * the target and values being animated, assuming they use ObjectAnimator objects with
   * property names that are known on the target object.</p>
   *
   * <p>This class, and the associated XML flag for containers, animateLayoutChanges="true",
   * provides a simple utility meant for automating changes in straightforward situations.
   * Using LayoutTransition at multiple levels of a nested view hierarchy may not work due to the
   * interrelationship of the various levels of layout. Also, a container that is being scrolled
   * at the same time as items are being added or removed is probably not a good candidate for
   * this utility, because the before/after locations calculated by LayoutTransition
   * may not match the actual locations when the animations finish due to the container
   * being scrolled as the animations are running. You can work around that
   * particular issue by disabling the 'changing' animations by setting the CHANGE_APPEARING
   * and CHANGE_DISAPPEARING animations to null, and setting the startDelay of the
   * other animations appropriately.</p>
   */
  public class LayoutTransition {
  
      /**
       * A flag indicating the animation that runs on those items that are changing
       * due to a new item appearing in the container.
       */
      public static final int CHANGE_APPEARING = 0;
  
      /**
       * A flag indicating the animation that runs on those items that are changing
       * due to an item disappearing from the container.
       */
      public static final int CHANGE_DISAPPEARING = 1;
  
      /**
       * A flag indicating the animation that runs on those items that are appearing
       * in the container.
       */
      public static final int APPEARING = 2;
  
      /**
       * A flag indicating the animation that runs on those items that are disappearing
       * from the container.
       */
      public static final int DISAPPEARING = 3;
  
      /**
       * A flag indicating the animation that runs on those items that are changing
       * due to a layout change not caused by items being added to or removed
       * from the container. This transition type is not enabled by default; it can be
       * enabled via {@link #enableTransitionType(int)}.
       */
      public static final int CHANGING = 4;
  
      /**
       * Private bit fields used to set the collection of enabled transition types for
       * mTransitionTypes.
       */
      private static final int FLAG_APPEARING             = 0x01;
      private static final int FLAG_DISAPPEARING          = 0x02;
      private static final int FLAG_CHANGE_APPEARING      = 0x04;
      private static final int FLAG_CHANGE_DISAPPEARING   = 0x08;
      private static final int FLAG_CHANGING              = 0x10;
  
      /**
       * These variables hold the animations that are currently used to run the transition effects.
       * These animations are set to defaults, but can be changed to custom animations by
       * calls to setAnimator().
       */
      private Animator mDisappearingAnim = null;
      private Animator mAppearingAnim = null;
      private Animator mChangingAppearingAnim = null;
      private Animator mChangingDisappearingAnim = null;
      private Animator mChangingAnim = null;
  
      /**
       * These are the default animations, defined in the constructor, that will be used
       * unless the user specifies custom animations.
       */
      private static ObjectAnimator defaultChange;
      private static ObjectAnimator defaultChangeIn;
      private static ObjectAnimator defaultChangeOut;
      private static ObjectAnimator defaultFadeIn;
      private static ObjectAnimator defaultFadeOut;
  
      /**
       * The default duration used by all animations.
       */
      private static long DEFAULT_DURATION = 300;
  
      /**
       * The durations of the different animations
       */
      private long mChangingAppearingDuration = DEFAULT_DURATION;
      private long mChangingDisappearingDuration = DEFAULT_DURATION;
      private long mChangingDuration = DEFAULT_DURATION;
      private long mAppearingDuration = DEFAULT_DURATION;
      private long mDisappearingDuration = DEFAULT_DURATION;
  
      /**
       * The start delays of the different animations. Note that the default behavior of
       * the appearing item is the default duration, since it should wait for the items to move
       * before fading it. Same for the changing animation when disappearing; it waits for the item
       * to fade out before moving the other items.
       */
      private long mAppearingDelay = DEFAULT_DURATION;
      private long mDisappearingDelay = 0;
      private long mChangingAppearingDelay = 0;
      private long mChangingDisappearingDelay = DEFAULT_DURATION;
      private long mChangingDelay = 0;
  
      /**
       * The inter-animation delays used on the changing animations
       */
      private long mChangingAppearingStagger = 0;
      private long mChangingDisappearingStagger = 0;
      private long mChangingStagger = 0;
  
      /**
       * Static interpolators - these are stateless and can be shared across the instances
       */
      private static TimeInterpolator ACCEL_DECEL_INTERPOLATOR =
              new AccelerateDecelerateInterpolator();
      private static TimeInterpolator DECEL_INTERPOLATOR = new DecelerateInterpolator();
      private static TimeInterpolator sAppearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
      private static TimeInterpolator sDisappearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
      private static TimeInterpolator sChangingAppearingInterpolator = DECEL_INTERPOLATOR;
      private static TimeInterpolator sChangingDisappearingInterpolator = DECEL_INTERPOLATOR;
      private static TimeInterpolator sChangingInterpolator = DECEL_INTERPOLATOR;
  
      /**
       * The default interpolators used for the animations
       */
      private TimeInterpolator mAppearingInterpolator = sAppearingInterpolator;
      private TimeInterpolator mDisappearingInterpolator = sDisappearingInterpolator;
      private TimeInterpolator mChangingAppearingInterpolator = sChangingAppearingInterpolator;
      private TimeInterpolator mChangingDisappearingInterpolator = sChangingDisappearingInterpolator;
      private TimeInterpolator mChangingInterpolator = sChangingInterpolator;
  
      /**
       * These hashmaps are used to store the animations that are currently running as part of
       * the transition. The reason for this is that a further layout event should cause
       * existing animations to stop where they are prior to starting new animations. So
       * we cache all of the current animations in this map for possible cancellation on
       * another layout event. LinkedHashMaps are used to preserve the order in which animations
       * are inserted, so that we process events (such as setting up start values) in the same order.
       */
      private final HashMap<View, Animator> pendingAnimations =
              new HashMap<View, Animator>();
      private final LinkedHashMap<View, Animator> currentChangingAnimations =
              new LinkedHashMap<View, Animator>();
      private final LinkedHashMap<View, Animator> currentAppearingAnimations =
              new LinkedHashMap<View, Animator>();
      private final LinkedHashMap<View, Animator> currentDisappearingAnimations =
              new LinkedHashMap<View, Animator>();
  
      /**
       * This hashmap is used to track the listeners that have been added to the children of
       * a container. When a layout change occurs, an animation is created for each View, so that
       * the pre-layout values can be cached in that animation. Then a listener is added to the
       * view to see whether the layout changes the bounds of that view. If so, the animation
       * is set with the final values and then run. If not, the animation is not started. When
       * the process of setting up and running all appropriate animations is done, we need to
       * remove these listeners and clear out the map.
       */
      private final HashMap<View, View.OnLayoutChangeListener> layoutChangeListenerMap =
              new HashMap<View, View.OnLayoutChangeListener>();
  
      /**
       * Used to track the current delay being assigned to successive animations as they are
       * started. This value is incremented for each new animation, then zeroed before the next
       * transition begins.
       */
      private long staggerDelay;
  
      /**
       * These are the types of transition animations that the LayoutTransition is reacting
       * to. By default, appearing/disappearing and the change animations related to them are
       * enabled (not CHANGING).
       */
      private int mTransitionTypes = FLAG_CHANGE_APPEARING | FLAG_CHANGE_DISAPPEARING |
              FLAG_APPEARING | FLAG_DISAPPEARING;
      /**
       * The set of listeners that should be notified when APPEARING/DISAPPEARING transitions
       * start and end.
       */
      private ArrayList<TransitionListener> mListeners;
  
      /**
       * Controls whether changing animations automatically animate the parent hierarchy as well.
       * This behavior prevents artifacts when wrap_content layouts snap to the end state as the
       * transition begins, causing visual glitches and clipping.
       * Default value is true.
       */
      private boolean mAnimateParentHierarchy = true;
  
  
      /**
       * Constructs a LayoutTransition object. By default, the object will listen to layout
       * events on any ViewGroup that it is set on and will run default animations for each
       * type of layout event.
       */
      public LayoutTransition() {
          if (defaultChangeIn == null) {
              // "left" is just a placeholder; we'll put real properties/values in when needed
              PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
              PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
              PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
              PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
              PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
              PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
              defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null,
                      pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
              defaultChangeIn.setDuration(DEFAULT_DURATION);
              defaultChangeIn.setStartDelay(mChangingAppearingDelay);
              defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
              defaultChangeOut = defaultChangeIn.clone();
              defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
              defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
              defaultChange = defaultChangeIn.clone();
              defaultChange.setStartDelay(mChangingDelay);
              defaultChange.setInterpolator(mChangingInterpolator);
  
              defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
              defaultFadeIn.setDuration(DEFAULT_DURATION);
              defaultFadeIn.setStartDelay(mAppearingDelay);
              defaultFadeIn.setInterpolator(mAppearingInterpolator);
              defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
              defaultFadeOut.setDuration(DEFAULT_DURATION);
              defaultFadeOut.setStartDelay(mDisappearingDelay);
              defaultFadeOut.setInterpolator(mDisappearingInterpolator);
          }
          mChangingAppearingAnim = defaultChangeIn;
          mChangingDisappearingAnim = defaultChangeOut;
          mChangingAnim = defaultChange;
          mAppearingAnim = defaultFadeIn;
          mDisappearingAnim = defaultFadeOut;
      }
  
      /**
       * Sets the duration to be used by all animations of this transition object. If you want to
       * set the duration of just one of the animations in particular, use the
       * {@link #setDuration(int, long)} method.
       *
       * @param duration The length of time, in milliseconds, that the transition animations
       * should last.
       */
      public void setDuration(long duration) {
          mChangingAppearingDuration = duration;
          mChangingDisappearingDuration = duration;
          mChangingDuration = duration;
          mAppearingDuration = duration;
          mDisappearingDuration = duration;
      }
  
      /**
       * Enables the specified transitionType for this LayoutTransition object.
       * By default, a LayoutTransition listens for changes in children being
       * added/remove/hidden/shown in the container, and runs the animations associated with
       * those events. That is, all transition types besides {@link #CHANGING} are enabled by default.
       * You can also enable {@link #CHANGING} animations by calling this method with the
       * {@link #CHANGING} transitionType.
       *
       * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
       * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
       */
      public void enableTransitionType(int transitionType) {
          switch (transitionType) {
              case APPEARING:
                  mTransitionTypes |= FLAG_APPEARING;
                  break;
              case DISAPPEARING:
                  mTransitionTypes |= FLAG_DISAPPEARING;
                  break;
              case CHANGE_APPEARING:
                  mTransitionTypes |= FLAG_CHANGE_APPEARING;
                  break;
              case CHANGE_DISAPPEARING:
                  mTransitionTypes |= FLAG_CHANGE_DISAPPEARING;
                  break;
              case CHANGING:
                  mTransitionTypes |= FLAG_CHANGING;
                  break;
          }
      }
  
      /**
       * Disables the specified transitionType for this LayoutTransition object.
       * By default, all transition types except {@link #CHANGING} are enabled.
       *
       * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
       * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
       */
      public void disableTransitionType(int transitionType) {
          switch (transitionType) {
              case APPEARING:
                  mTransitionTypes &= ~FLAG_APPEARING;
                  break;
              case DISAPPEARING:
                  mTransitionTypes &= ~FLAG_DISAPPEARING;
                  break;
              case CHANGE_APPEARING:
                  mTransitionTypes &= ~FLAG_CHANGE_APPEARING;
                  break;
              case CHANGE_DISAPPEARING:
                  mTransitionTypes &= ~FLAG_CHANGE_DISAPPEARING;
                  break;
              case CHANGING:
                  mTransitionTypes &= ~FLAG_CHANGING;
                  break;
          }
      }
  
      /**
       * Returns whether the specified transitionType is enabled for this LayoutTransition object.
       * By default, all transition types except {@link #CHANGING} are enabled.
       *
       * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
       * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
       * @return true if the specified transitionType is currently enabled, false otherwise.
       */
      public boolean isTransitionTypeEnabled(int transitionType) {
          switch (transitionType) {
              case APPEARING:
                  return (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING;
              case DISAPPEARING:
                  return (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING;
              case CHANGE_APPEARING:
                  return (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING;
              case CHANGE_DISAPPEARING:
                  return (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING;
              case CHANGING:
                  return (mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING;
          }
          return false;
      }
  
      /**
       * Sets the start delay on one of the animation objects used by this transition. The
       * <code>transitionType</code> parameter determines the animation whose start delay
       * is being set.
       *
       * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
       * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
       * the animation whose start delay is being set.
       * @param delay The length of time, in milliseconds, to delay before starting the animation.
       * @see Animator#setStartDelay(long)
       */
      public void setStartDelay(int transitionType, long delay) {
          switch (transitionType) {
              case CHANGE_APPEARING:
                  mChangingAppearingDelay = delay;
                  break;
              case CHANGE_DISAPPEARING:
                  mChangingDisappearingDelay = delay;
                  break;
              case CHANGING:
                  mChangingDelay = delay;
                  break;
              case APPEARING:
                  mAppearingDelay = delay;
                  break;
              case DISAPPEARING:
                  mDisappearingDelay = delay;
                  break;
          }
      }
  
      /**
       * Gets the start delay on one of the animation objects used by this transition. The
       * <code>transitionType</code> parameter determines the animation whose start delay
       * is returned.
       *
       * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
       * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
       * the animation whose start delay is returned.
       * @return long The start delay of the specified animation.
       * @see Animator#getStartDelay()
       */
      public long getStartDelay(int transitionType) {
          switch (transitionType) {
              case CHANGE_APPEARING:
                  return mChangingAppearingDelay;
              case CHANGE_DISAPPEARING:
                  return mChangingDisappearingDelay;
              case CHANGING:
                  return mChangingDelay;
              case APPEARING:
                  return mAppearingDelay;
              case DISAPPEARING:
                  return mDisappearingDelay;
          }
          // shouldn't reach here
          return 0;
      }
  
      /**
       * Sets the duration on one of the animation objects used by this transition. The
       * <code>transitionType</code> parameter determines the animation whose duration
       * is being set.
       *
       * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
       * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
       * the animation whose duration is being set.
       * @param duration The length of time, in milliseconds, that the specified animation should run.
       * @see Animator#setDuration(long)
       */
      public void setDuration(int transitionType, long duration) {
          switch (transitionType) {
              case CHANGE_APPEARING:
                  mChangingAppearingDuration = duration;
                  break;
              case CHANGE_DISAPPEARING:
                  mChangingDisappearingDuration = duration;
                  break;
              case CHANGING:
                  mChangingDuration = duration;
                  break;
              case APPEARING:
                  mAppearingDuration = duration;
                  break;
              case DISAPPEARING:
                  mDisappearingDuration = duration;
                  break;
          }
      }
  
      /**
       * Gets the duration on one of the animation objects used by this transition. The
       * <code>transitionType</code> parameter determines the animation whose duration
       * is returned.
       *
       * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
       * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
       * the animation whose duration is returned.
       * @return long The duration of the specified animation.
       * @see Animator#getDuration()
       */
      public long getDuration(int transitionType) {
          switch (transitionType) {
              case CHANGE_APPEARING:
                  return mChangingAppearingDuration;
              case CHANGE_DISAPPEARING:
                  return mChangingDisappearingDuration;
              case CHANGING:
                  return mChangingDuration;
              case APPEARING:
                  return mAppearingDuration;
              case DISAPPEARING:
                  return mDisappearingDuration;
          }
          // shouldn't reach here
          return 0;
      }
  
      /**
       * Sets the length of time to delay between starting each animation during one of the
       * change animations.
       *
       * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or
       * {@link #CHANGING}.
       * @param duration The length of time, in milliseconds, to delay before launching the next
       * animation in the sequence.
       */
      public void setStagger(int transitionType, long duration) {
          switch (transitionType) {
              case CHANGE_APPEARING:
                  mChangingAppearingStagger = duration;
                  break;
              case CHANGE_DISAPPEARING:
                  mChangingDisappearingStagger = duration;
                  break;
              case CHANGING:
                  mChangingStagger = duration;
                  break;
              // noop other cases
          }
      }
  
      /**
       * Gets the length of time to delay between starting each animation during one of the
       * change animations.
       *
       * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or
       * {@link #CHANGING}.
       * @return long The length of time, in milliseconds, to delay before launching the next
       * animation in the sequence.
       */
      public long getStagger(int transitionType) {
          switch (transitionType) {
              case CHANGE_APPEARING:
                  return mChangingAppearingStagger;
              case CHANGE_DISAPPEARING:
                  return mChangingDisappearingStagger;
              case CHANGING:
                  return mChangingStagger;
          }
          // shouldn't reach here
          return 0;
      }
  
      /**
       * Sets the interpolator on one of the animation objects used by this transition. The
       * <code>transitionType</code> parameter determines the animation whose interpolator
       * is being set.
       *
       * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
       * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
       * the animation whose interpolator is being set.
       * @param interpolator The interpolator that the specified animation should use.
       * @see Animator#setInterpolator(TimeInterpolator)
       */
      public void setInterpolator(int transitionType, TimeInterpolator interpolator) {
          switch (transitionType) {
              case CHANGE_APPEARING:
                  mChangingAppearingInterpolator = interpolator;
                  break;
              case CHANGE_DISAPPEARING:
                  mChangingDisappearingInterpolator = interpolator;
                  break;
              case CHANGING:
                  mChangingInterpolator = interpolator;
                  break;
              case APPEARING:
                  mAppearingInterpolator = interpolator;
                  break;
              case DISAPPEARING:
                  mDisappearingInterpolator = interpolator;
                  break;
          }
      }
  
      /**
       * Gets the interpolator on one of the animation objects used by this transition. The
       * <code>transitionType</code> parameter determines the animation whose interpolator
       * is returned.
       *
       * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
       * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
       * the animation whose interpolator is being returned.
       * @return TimeInterpolator The interpolator that the specified animation uses.
       * @see Animator#setInterpolator(TimeInterpolator)
       */
      public TimeInterpolator getInterpolator(int transitionType) {
          switch (transitionType) {
              case CHANGE_APPEARING:
                  return mChangingAppearingInterpolator;
              case CHANGE_DISAPPEARING:
                  return mChangingDisappearingInterpolator;
              case CHANGING:
                  return mChangingInterpolator;
              case APPEARING:
                  return mAppearingInterpolator;
              case DISAPPEARING:
                  return mDisappearingInterpolator;
          }
          // shouldn't reach here
          return null;
      }
  
      /**
       * Sets the animation used during one of the transition types that may run. Any
       * Animator object can be used, but to be most useful in the context of layout
       * transitions, the animation should either be a ObjectAnimator or a AnimatorSet
       * of animations including PropertyAnimators. Also, these ObjectAnimator objects
       * should be able to get and set values on their target objects automatically. For
       * example, a ObjectAnimator that animates the property "left" is able to set and get the
       * <code>left</code> property from the View objects being animated by the layout
       * transition. The transition works by setting target objects and properties
       * dynamically, according to the pre- and post-layoout values of those objects, so
       * having animations that can handle those properties appropriately will work best
       * for custom animation. The dynamic setting of values is only the case for the
       * CHANGE animations; the APPEARING and DISAPPEARING animations are simply run with
       * the values they have.
       *
       * <p>It is also worth noting that any and all animations (and their underlying
       * PropertyValuesHolder objects) will have their start and end values set according
       * to the pre- and post-layout values. So, for example, a custom animation on "alpha"
       * as the CHANGE_APPEARING animation will inherit the real value of alpha on the target
       * object (presumably 1) as its starting and ending value when the animation begins.
       * Animations which need to use values at the beginning and end that may not match the
       * values queried when the transition begins may need to use a different mechanism
       * than a standard ObjectAnimator object.</p>
       *
       * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
       * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the
       * animation whose animator is being set.
       * @param animator The animation being assigned. A value of <code>null</code> means that no
       * animation will be run for the specified transitionType.
       */
      public void setAnimator(int transitionType, Animator animator) {
          switch (transitionType) {
              case CHANGE_APPEARING:
                  mChangingAppearingAnim = animator;
                  break;
              case CHANGE_DISAPPEARING:
                  mChangingDisappearingAnim = animator;
                  break;
              case CHANGING:
                  mChangingAnim = animator;
                  break;
              case APPEARING:
                  mAppearingAnim = animator;
                  break;
              case DISAPPEARING:
                  mDisappearingAnim = animator;
                  break;
          }
      }
  
      /**
       * Gets the animation used during one of the transition types that may run.
       *
       * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
       * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
       * the animation whose animator is being returned.
       * @return Animator The animation being used for the given transition type.
       * @see #setAnimator(int, Animator)
       */
      public Animator getAnimator(int transitionType) {
          switch (transitionType) {
              case CHANGE_APPEARING:
                  return mChangingAppearingAnim;
              case CHANGE_DISAPPEARING:
                  return mChangingDisappearingAnim;
              case CHANGING:
                  return mChangingAnim;
              case APPEARING:
                  return mAppearingAnim;
              case DISAPPEARING:
                  return mDisappearingAnim;
          }
          // shouldn't reach here
          return null;
      }
  
      /**
       * This function sets up animations on all of the views that change during layout.
       * For every child in the parent, we create a change animation of the appropriate
       * type (appearing, disappearing, or changing) and ask it to populate its start values from its
       * target view. We add layout listeners to all child views and listen for changes. For
       * those views that change, we populate the end values for those animations and start them.
       * Animations are not run on unchanging views.
       *
       * @param parent The container which is undergoing a change.
       * @param newView The view being added to or removed from the parent. May be null if the
       * changeReason is CHANGING.
       * @param changeReason A value of APPEARING, DISAPPEARING, or CHANGING, indicating whether the
       * transition is occurring because an item is being added to or removed from the parent, or
       * if it is running in response to a layout operation (that is, if the value is CHANGING).
       */
      private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) {
  
          Animator baseAnimator = null;
          Animator parentAnimator = null;
          final long duration;
          switch (changeReason) {
              case APPEARING:
                  baseAnimator = mChangingAppearingAnim;
                  duration = mChangingAppearingDuration;
                  parentAnimator = defaultChangeIn;
                  break;
              case DISAPPEARING:
                  baseAnimator = mChangingDisappearingAnim;
                  duration = mChangingDisappearingDuration;
                  parentAnimator = defaultChangeOut;
                  break;
              case CHANGING:
                  baseAnimator = mChangingAnim;
                  duration = mChangingDuration;
                  parentAnimator = defaultChange;
                  break;
              default:
                  // Shouldn't reach here
                  duration = 0;
                  break;
          }
          // If the animation is null, there's nothing to do
          if (baseAnimator == null) {
              return;
          }
  
          // reset the inter-animation delay, in case we use it later
          staggerDelay = 0;
  
          final ViewTreeObserver observer = parent.getViewTreeObserver();
          if (!observer.isAlive()) {
              // If the observer's not in a good state, skip the transition
              return;
          }
          int numChildren = parent.getChildCount();
  
          for (int i = 0; i < numChildren; ++i) {
              final View child = parent.getChildAt(i);
  
              // only animate the views not being added or removed
              if (child != newView) {
                  setupChangeAnimation(parent, changeReason, baseAnimator, duration, child);
              }
          }
          if (mAnimateParentHierarchy) {
              ViewGroup tempParent = parent;
              while (tempParent != null) {
                  ViewParent parentParent = tempParent.getParent();
                  if (parentParent instanceof ViewGroup) {
                      setupChangeAnimation((ViewGroup)parentParent, changeReason, parentAnimator,
                              duration, tempParent);
                      tempParent = (ViewGroup) parentParent;
                  } else {
                      tempParent = null;
                  }
  
              }
          }
  
          // This is the cleanup step. When we get this rendering event, we know that all of
          // the appropriate animations have been set up and run. Now we can clear out the
          // layout listeners.
          CleanupCallback callback = new CleanupCallback(layoutChangeListenerMap, parent);
          observer.addOnPreDrawListener(callback);
          parent.addOnAttachStateChangeListener(callback);
      }
  
      /**
       * This flag controls whether CHANGE_APPEARING or CHANGE_DISAPPEARING animations will
       * cause the default changing animation to be run on the parent hierarchy as well. This allows
       * containers of transitioning views to also transition, which may be necessary in situations
       * where the containers bounds change between the before/after states and may clip their
       * children during the transition animations. For example, layouts with wrap_content will
       * adjust their bounds according to the dimensions of their children.
       *
       * <p>The default changing transitions animate the bounds and scroll positions of the
       * target views. These are the animations that will run on the parent hierarchy, not
       * the custom animations that happen to be set on the transition. This allows custom
       * behavior for the children of the transitioning container, but uses standard behavior
       * of resizing/rescrolling on any changing parents.
       *
       * @param animateParentHierarchy A boolean value indicating whether the parents of
       * transitioning views should also be animated during the transition. Default value is true.
       */
      public void setAnimateParentHierarchy(boolean animateParentHierarchy) {
          mAnimateParentHierarchy = animateParentHierarchy;
      }
  
      /**
       * Utility function called by runChangingTransition for both the children and the parent
       * hierarchy.
       */
      private void setupChangeAnimation(final ViewGroup parent, final int changeReason,
              Animator baseAnimator, final long duration, final View child) {
  
          // If we already have a listener for this child, then we've already set up the
          // changing animation we need. Multiple calls for a child may occur when several
          // add/remove operations are run at once on a container; each one will trigger
          // changes for the existing children in the container.
          if (layoutChangeListenerMap.get(child) != null) {
              return;
          }
  
          // Don't animate items up from size(0,0); this is likely because the objects
          // were offscreen/invisible or otherwise measured to be infinitely small. We don't
          // want to see them animate into their real size; just ignore animation requests
          // on these views
          if (child.getWidth() == 0 && child.getHeight() == 0) {
              return;
          }
  
          // Make a copy of the appropriate animation
          final Animator anim = baseAnimator.clone();
  
          // Set the target object for the animation
          anim.setTarget(child);
  
          // A ObjectAnimator (or AnimatorSet of them) can extract start values from
          // its target object
          anim.setupStartValues();
  
          // If there's an animation running on this view already, cancel it
          Animator currentAnimation = pendingAnimations.get(child);
          if (currentAnimation != null) {
              currentAnimation.cancel();
              pendingAnimations.remove(child);
          }
          // Cache the animation in case we need to cancel it later
          pendingAnimations.put(child, anim);
  
          // For the animations which don't get started, we have to have a means of
          // removing them from the cache, lest we leak them and their target objects.
          // We run an animator for the default duration+100 (an arbitrary time, but one
          // which should far surpass the delay between setting them up here and
          // handling layout events which start them.
          ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
                  setDuration(duration + 100);
          pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationEnd(Animator animation) {
                  pendingAnimations.remove(child);
              }
          });
          pendingAnimRemover.start();
  
          // Add a listener to track layout changes on this view. If we don't get a callback,
          // then there's nothing to animate.
          final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
              public void onLayoutChange(View v, int left, int top, int right, int bottom,
                      int oldLeft, int oldTop, int oldRight, int oldBottom) {
  
                  // Tell the animation to extract end values from the changed object
                  anim.setupEndValues();
                  if (anim instanceof ValueAnimator) {
                      boolean valuesDiffer = false;
                      ValueAnimator valueAnim = (ValueAnimator)anim;
                      PropertyValuesHolder[] oldValues = valueAnim.getValues();
                      for (int i = 0; i < oldValues.length; ++i) {
                          PropertyValuesHolder pvh = oldValues[i];
                          if (pvh.mKeyframes instanceof KeyframeSet) {
                              KeyframeSet keyframeSet = (KeyframeSet) pvh.mKeyframes;
                              if (keyframeSet.mFirstKeyframe == null ||
                                      keyframeSet.mLastKeyframe == null ||
                                      !keyframeSet.mFirstKeyframe.getValue().equals(
                                              keyframeSet.mLastKeyframe.getValue())) {
                                  valuesDiffer = true;
                              }
                          } else if (!pvh.mKeyframes.getValue(0).equals(pvh.mKeyframes.getValue(1))) {
                              valuesDiffer = true;
                          }
                      }
                      if (!valuesDiffer) {
                          return;
                      }
                  }
  
                  long startDelay = 0;
                  switch (changeReason) {
                      case APPEARING:
                          startDelay = mChangingAppearingDelay + staggerDelay;
                          staggerDelay += mChangingAppearingStagger;
                          if (mChangingAppearingInterpolator != sChangingAppearingInterpolator) {
                              anim.setInterpolator(mChangingAppearingInterpolator);
                          }
                          break;
                      case DISAPPEARING:
                          startDelay = mChangingDisappearingDelay + staggerDelay;
                          staggerDelay += mChangingDisappearingStagger;
                          if (mChangingDisappearingInterpolator !=
                                  sChangingDisappearingInterpolator) {
                              anim.setInterpolator(mChangingDisappearingInterpolator);
                          }
                          break;
                      case CHANGING:
                          startDelay = mChangingDelay + staggerDelay;
                          staggerDelay += mChangingStagger;
                          if (mChangingInterpolator != sChangingInterpolator) {
                              anim.setInterpolator(mChangingInterpolator);
                          }
                          break;
                  }
                  anim.setStartDelay(startDelay);
                  anim.setDuration(duration);
  
                  Animator prevAnimation = currentChangingAnimations.get(child);
                  if (prevAnimation != null) {
                      prevAnimation.cancel();
                  }
                  Animator pendingAnimation = pendingAnimations.get(child);
                  if (pendingAnimation != null) {
                      pendingAnimations.remove(child);
                  }
                  // Cache the animation in case we need to cancel it later
                  currentChangingAnimations.put(child, anim);
  
                  parent.requestTransitionStart(LayoutTransition.this);
  
                  // this only removes listeners whose views changed - must clear the
                  // other listeners later
                  child.removeOnLayoutChangeListener(this);
                  layoutChangeListenerMap.remove(child);
              }
          };
          // Remove the animation from the cache when it ends
          anim.addListener(new AnimatorListenerAdapter() {
  
              @Override
              public void onAnimationStart(Animator animator) {
                  if (hasListeners()) {
                      ArrayList<TransitionListener> listeners =
                              (ArrayList<TransitionListener>) mListeners.clone();
                      for (TransitionListener listener : listeners) {
                          listener.startTransition(LayoutTransition.this, parent, child,
                                  changeReason == APPEARING ?
                                          CHANGE_APPEARING : changeReason == DISAPPEARING ?
                                          CHANGE_DISAPPEARING : CHANGING);
                      }
                  }
              }
  
              @Override
              public void onAnimationCancel(Animator animator) {
                  child.removeOnLayoutChangeListener(listener);
                  layoutChangeListenerMap.remove(child);
              }
  
              @Override
              public void onAnimationEnd(Animator animator) {
                  currentChangingAnimations.remove(child);
                  if (hasListeners()) {
                      ArrayList<TransitionListener> listeners =
                              (ArrayList<TransitionListener>) mListeners.clone();
                      for (TransitionListener listener : listeners) {
                          listener.endTransition(LayoutTransition.this, parent, child,
                                  changeReason == APPEARING ?
                                          CHANGE_APPEARING : changeReason == DISAPPEARING ?
                                          CHANGE_DISAPPEARING : CHANGING);
                      }
                  }
              }
          });
  
          child.addOnLayoutChangeListener(listener);
          // cache the listener for later removal
          layoutChangeListenerMap.put(child, listener);
      }
  
      /**
       * Starts the animations set up for a CHANGING transition. We separate the setup of these
       * animations from actually starting them, to avoid side-effects that starting the animations
       * may have on the properties of the affected objects. After setup, we tell the affected parent
       * that this transition should be started. The parent informs its ViewAncestor, which then
       * starts the transition after the current layout/measurement phase, just prior to drawing
       * the view hierarchy.
       *
       * @hide
       */
      public void startChangingAnimations() {
          LinkedHashMap<View, Animator> currentAnimCopy =
                  (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
          for (Animator anim : currentAnimCopy.values()) {
              if (anim instanceof ObjectAnimator) {
                  ((ObjectAnimator) anim).setCurrentPlayTime(0);
              }
              anim.start();
          }
      }
  
      /**
       * Ends the animations that are set up for a CHANGING transition. This is a variant of
       * startChangingAnimations() which is called when the window the transition is playing in
       * is not visible. We need to make sure the animations put their targets in their end states
       * and that the transition finishes to remove any mid-process state (such as isRunning()).
       *
       * @hide
       */
      public void endChangingAnimations() {
          LinkedHashMap<View, Animator> currentAnimCopy =
                  (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
          for (Animator anim : currentAnimCopy.values()) {
              anim.start();
              anim.end();
          }
          // listeners should clean up the currentChangingAnimations list, but just in case...
          currentChangingAnimations.clear();
      }
  
      /**
       * Returns true if animations are running which animate layout-related properties. This
       * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
       * are running, since these animations operate on layout-related properties.
       *
       * @return true if CHANGE_APPEARING or CHANGE_DISAPPEARING animations are currently
       * running.
       */
      public boolean isChangingLayout() {
          return (currentChangingAnimations.size() > 0);
      }
  
      /**
       * Returns true if any of the animations in this transition are currently running.
       *
       * @return true if any animations in the transition are running.
       */
      public boolean isRunning() {
          return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 ||
                  currentDisappearingAnimations.size() > 0);
      }
  
      /**
       * Cancels the currently running transition. Note that we cancel() the changing animations
       * but end() the visibility animations. This is because this method is currently called
       * in the context of starting a new transition, so we want to move things from their mid-
       * transition positions, but we want them to have their end-transition visibility.
       *
       * @hide
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
      public void cancel() {
          if (currentChangingAnimations.size() > 0) {
              LinkedHashMap<View, Animator> currentAnimCopy =
                      (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
              for (Animator anim : currentAnimCopy.values()) {
                  anim.cancel();
              }
              currentChangingAnimations.clear();
          }
          if (currentAppearingAnimations.size() > 0) {
              LinkedHashMap<View, Animator> currentAnimCopy =
                      (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
              for (Animator anim : currentAnimCopy.values()) {
                  anim.end();
              }
              currentAppearingAnimations.clear();
          }
          if (currentDisappearingAnimations.size() > 0) {
              LinkedHashMap<View, Animator> currentAnimCopy =
                      (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
              for (Animator anim : currentAnimCopy.values()) {
                  anim.end();
              }
              currentDisappearingAnimations.clear();
          }
      }
  
      /**
       * Cancels the specified type of transition. Note that we cancel() the changing animations
       * but end() the visibility animations. This is because this method is currently called
       * in the context of starting a new transition, so we want to move things from their mid-
       * transition positions, but we want them to have their end-transition visibility.
       *
       * @hide
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
      public void cancel(int transitionType) {
          switch (transitionType) {
              case CHANGE_APPEARING:
              case CHANGE_DISAPPEARING:
              case CHANGING:
                  if (currentChangingAnimations.size() > 0) {
                      LinkedHashMap<View, Animator> currentAnimCopy =
                              (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
                      for (Animator anim : currentAnimCopy.values()) {
                          anim.cancel();
                      }
                      currentChangingAnimations.clear();
                  }
                  break;
              case APPEARING:
                  if (currentAppearingAnimations.size() > 0) {
                      LinkedHashMap<View, Animator> currentAnimCopy =
                              (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
                      for (Animator anim : currentAnimCopy.values()) {
                          anim.end();
                      }
                      currentAppearingAnimations.clear();
                  }
                  break;
              case DISAPPEARING:
                  if (currentDisappearingAnimations.size() > 0) {
                      LinkedHashMap<View, Animator> currentAnimCopy =
                              (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
                      for (Animator anim : currentAnimCopy.values()) {
                          anim.end();
                      }
                      currentDisappearingAnimations.clear();
                  }
                  break;
          }
      }
  
      /**
       * This method runs the animation that makes an added item appear.
       *
       * @param parent The ViewGroup to which the View is being added.
       * @param child The View being added to the ViewGroup.
       */
      private void runAppearingTransition(final ViewGroup parent, final View child) {
          Animator currentAnimation = currentDisappearingAnimations.get(child);
          if (currentAnimation != null) {
              currentAnimation.cancel();
          }
          if (mAppearingAnim == null) {
              if (hasListeners()) {
                  ArrayList<TransitionListener> listeners =
                          (ArrayList<TransitionListener>) mListeners.clone();
                  for (TransitionListener listener : listeners) {
                      listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
                  }
              }
              return;
          }
          Animator anim = mAppearingAnim.clone();
          anim.setTarget(child);
          anim.setStartDelay(mAppearingDelay);
          anim.setDuration(mAppearingDuration);
          if (mAppearingInterpolator != sAppearingInterpolator) {
              anim.setInterpolator(mAppearingInterpolator);
          }
          if (anim instanceof ObjectAnimator) {
              ((ObjectAnimator) anim).setCurrentPlayTime(0);
          }
          anim.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationEnd(Animator anim) {
                  currentAppearingAnimations.remove(child);
                  if (hasListeners()) {
                      ArrayList<TransitionListener> listeners =
                              (ArrayList<TransitionListener>) mListeners.clone();
                      for (TransitionListener listener : listeners) {
                          listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
                      }
                  }
              }
          });
          currentAppearingAnimations.put(child, anim);
          anim.start();
      }
  
      /**
       * This method runs the animation that makes a removed item disappear.
       *
       * @param parent The ViewGroup from which the View is being removed.
       * @param child The View being removed from the ViewGroup.
       */
      private void runDisappearingTransition(final ViewGroup parent, final View child) {
          Animator currentAnimation = currentAppearingAnimations.get(child);
          if (currentAnimation != null) {
              currentAnimation.cancel();
          }
          if (mDisappearingAnim == null) {
              if (hasListeners()) {
                  ArrayList<TransitionListener> listeners =
                          (ArrayList<TransitionListener>) mListeners.clone();
                  for (TransitionListener listener : listeners) {
                      listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
                  }
              }
              return;
          }
          Animator anim = mDisappearingAnim.clone();
          anim.setStartDelay(mDisappearingDelay);
          anim.setDuration(mDisappearingDuration);
          if (mDisappearingInterpolator != sDisappearingInterpolator) {
              anim.setInterpolator(mDisappearingInterpolator);
          }
          anim.setTarget(child);
          final float preAnimAlpha = child.getAlpha();
          anim.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationEnd(Animator anim) {
                  currentDisappearingAnimations.remove(child);
                  child.setAlpha(preAnimAlpha);
                  if (hasListeners()) {
                      ArrayList<TransitionListener> listeners =
                              (ArrayList<TransitionListener>) mListeners.clone();
                      for (TransitionListener listener : listeners) {
                          listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
                      }
                  }
              }
          });
          if (anim instanceof ObjectAnimator) {
              ((ObjectAnimator) anim).setCurrentPlayTime(0);
          }
          currentDisappearingAnimations.put(child, anim);
          anim.start();
      }
  
      /**
       * This method is called by ViewGroup when a child view is about to be added to the
       * container. This callback starts the process of a transition; we grab the starting
       * values, listen for changes to all of the children of the container, and start appropriate
       * animations.
       *
       * @param parent The ViewGroup to which the View is being added.
       * @param child The View being added to the ViewGroup.
       * @param changesLayout Whether the removal will cause changes in the layout of other views
       * in the container. INVISIBLE views becoming VISIBLE will not cause changes and thus will not
       * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
       */
      private void addChild(ViewGroup parent, View child, boolean changesLayout) {
          if (parent.getWindowVisibility() != View.VISIBLE) {
              return;
          }
          if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
              // Want disappearing animations to finish up before proceeding
              cancel(DISAPPEARING);
          }
          if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
              // Also, cancel changing animations so that we start fresh ones from current locations
              cancel(CHANGE_APPEARING);
              cancel(CHANGING);
          }
          if (hasListeners() && (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
              ArrayList<TransitionListener> listeners =
                      (ArrayList<TransitionListener>) mListeners.clone();
              for (TransitionListener listener : listeners) {
                  listener.startTransition(this, parent, child, APPEARING);
              }
          }
          if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
              runChangeTransition(parent, child, APPEARING);
          }
          if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
              runAppearingTransition(parent, child);
          }
      }
  
      private boolean hasListeners() {
          return mListeners != null && mListeners.size() > 0;
      }
  
      /**
       * This method is called by ViewGroup when there is a call to layout() on the container
       * with this LayoutTransition. If the CHANGING transition is enabled and if there is no other
       * transition currently running on the container, then this call runs a CHANGING transition.
       * The transition does not start immediately; it just sets up the mechanism to run if any
       * of the children of the container change their layout parameters (similar to
       * the CHANGE_APPEARING and CHANGE_DISAPPEARING transitions).
       *
       * @param parent The ViewGroup whose layout() method has been called.
       *
       * @hide
       */
      public void layoutChange(ViewGroup parent) {
          if (parent.getWindowVisibility() != View.VISIBLE) {
              return;
          }
          if ((mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING  && !isRunning()) {
              // This method is called for all calls to layout() in the container, including
              // those caused by add/remove/hide/show events, which will already have set up
              // transition animations. Avoid setting up CHANGING animations in this case; only
              // do so when there is not a transition already running on the container.
              runChangeTransition(parent, null, CHANGING);
          }
      }
  
      /**
       * This method is called by ViewGroup when a child view is about to be added to the
       * container. This callback starts the process of a transition; we grab the starting
       * values, listen for changes to all of the children of the container, and start appropriate
       * animations.
       *
       * @param parent The ViewGroup to which the View is being added.
       * @param child The View being added to the ViewGroup.
       */
      public void addChild(ViewGroup parent, View child) {
          addChild(parent, child, true);
      }
  
      /**
       * @deprecated Use {@link #showChild(android.view.ViewGroup, android.view.View, int)}.
       */
      @Deprecated
      public void showChild(ViewGroup parent, View child) {
          addChild(parent, child, true);
      }
  
      /**
       * This method is called by ViewGroup when a child view is about to be made visible in the
       * container. This callback starts the process of a transition; we grab the starting
       * values, listen for changes to all of the children of the container, and start appropriate
       * animations.
       *
       * @param parent The ViewGroup in which the View is being made visible.
       * @param child The View being made visible.
       * @param oldVisibility The previous visibility value of the child View, either
       * {@link View#GONE} or {@link View#INVISIBLE}.
       */
      public void showChild(ViewGroup parent, View child, int oldVisibility) {
          addChild(parent, child, oldVisibility == View.GONE);
      }
  
      /**
       * This method is called by ViewGroup when a child view is about to be removed from the
       * container. This callback starts the process of a transition; we grab the starting
       * values, listen for changes to all of the children of the container, and start appropriate
       * animations.
       *
       * @param parent The ViewGroup from which the View is being removed.
       * @param child The View being removed from the ViewGroup.
       * @param changesLayout Whether the removal will cause changes in the layout of other views
       * in the container. Views becoming INVISIBLE will not cause changes and thus will not
       * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
       */
      private void removeChild(ViewGroup parent, View child, boolean changesLayout) {
          if (parent.getWindowVisibility() != View.VISIBLE) {
              return;
          }
          if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
              // Want appearing animations to finish up before proceeding
              cancel(APPEARING);
          }
          if (changesLayout &&
                  (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
              // Also, cancel changing animations so that we start fresh ones from current locations
              cancel(CHANGE_DISAPPEARING);
              cancel(CHANGING);
          }
          if (hasListeners() && (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
              ArrayList<TransitionListener> listeners = (ArrayList<TransitionListener>) mListeners
                      .clone();
              for (TransitionListener listener : listeners) {
                  listener.startTransition(this, parent, child, DISAPPEARING);
              }
          }
          if (changesLayout &&
                  (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
              runChangeTransition(parent, child, DISAPPEARING);
          }
          if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
              runDisappearingTransition(parent, child);
          }
      }
  
      /**
       * This method is called by ViewGroup when a child view is about to be removed from the
       * container. This callback starts the process of a transition; we grab the starting
       * values, listen for changes to all of the children of the container, and start appropriate
       * animations.
       *
       * @param parent The ViewGroup from which the View is being removed.
       * @param child The View being removed from the ViewGroup.
       */
      public void removeChild(ViewGroup parent, View child) {
          removeChild(parent, child, true);
      }
  
      /**
       * @deprecated Use {@link #hideChild(android.view.ViewGroup, android.view.View, int)}.
       */
      @Deprecated
      public void hideChild(ViewGroup parent, View child) {
          removeChild(parent, child, true);
      }
  
      /**
       * This method is called by ViewGroup when a child view is about to be hidden in
       * container. This callback starts the process of a transition; we grab the starting
       * values, listen for changes to all of the children of the container, and start appropriate
       * animations.
       *
       * @param parent The parent ViewGroup of the View being hidden.
       * @param child The View being hidden.
       * @param newVisibility The new visibility value of the child View, either
       * {@link View#GONE} or {@link View#INVISIBLE}.
       */
      public void hideChild(ViewGroup parent, View child, int newVisibility) {
          removeChild(parent, child, newVisibility == View.GONE);
      }
  
      /**
       * Add a listener that will be called when the bounds of the view change due to
       * layout processing.
       *
       * @param listener The listener that will be called when layout bounds change.
       */
      public void addTransitionListener(TransitionListener listener) {
          if (mListeners == null) {
              mListeners = new ArrayList<TransitionListener>();
          }
          mListeners.add(listener);
      }
  
      /**
       * Remove a listener for layout changes.
       *
       * @param listener The listener for layout bounds change.
       */
      public void removeTransitionListener(TransitionListener listener) {
          if (mListeners == null) {
              return;
          }
          mListeners.remove(listener);
      }
  
      /**
       * Gets the current list of listeners for layout changes.
       * @return
       */
      public List<TransitionListener> getTransitionListeners() {
          return mListeners;
      }
  
      /**
       * This interface is used for listening to starting and ending events for transitions.
       */
      public interface TransitionListener {
  
          /**
           * This event is sent to listeners when any type of transition animation begins.
           *
           * @param transition The LayoutTransition sending out the event.
           * @param container The ViewGroup on which the transition is playing.
           * @param view The View object being affected by the transition animation.
           * @param transitionType The type of transition that is beginning,
           * {@link android.animation.LayoutTransition#APPEARING},
           * {@link android.animation.LayoutTransition#DISAPPEARING},
           * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
           * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
           */
          public void startTransition(LayoutTransition transition, ViewGroup container,
                  View view, int transitionType);
  
          /**
           * This event is sent to listeners when any type of transition animation ends.
           *
           * @param transition The LayoutTransition sending out the event.
           * @param container The ViewGroup on which the transition is playing.
           * @param view The View object being affected by the transition animation.
           * @param transitionType The type of transition that is ending,
           * {@link android.animation.LayoutTransition#APPEARING},
           * {@link android.animation.LayoutTransition#DISAPPEARING},
           * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
           * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
           */
          public void endTransition(LayoutTransition transition, ViewGroup container,
                  View view, int transitionType);
      }
  
      /**
       * Utility class to clean up listeners after animations are setup. Cleanup happens
       * when either the OnPreDrawListener method is called or when the parent is detached,
       * whichever comes first.
       */
      private static final class CleanupCallback implements ViewTreeObserver.OnPreDrawListener,
              View.OnAttachStateChangeListener {
  
          final Map<View, View.OnLayoutChangeListener> layoutChangeListenerMap;
          final ViewGroup parent;
  
          CleanupCallback(Map<View, View.OnLayoutChangeListener> listenerMap, ViewGroup parent) {
              this.layoutChangeListenerMap = listenerMap;
              this.parent = parent;
          }
  
          private void cleanup() {
              parent.getViewTreeObserver().removeOnPreDrawListener(this);
              parent.removeOnAttachStateChangeListener(this);
              int count = layoutChangeListenerMap.size();
              if (count > 0) {
                  Collection<View> views = layoutChangeListenerMap.keySet();
                  for (View view : views) {
                      View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view);
                      view.removeOnLayoutChangeListener(listener);
                  }
                  layoutChangeListenerMap.clear();
              }
          }
  
          @Override
          public void onViewAttachedToWindow(View v) {
          }
  
          @Override
          public void onViewDetachedFromWindow(View v) {
              cleanup();
          }
  
          @Override
          public boolean onPreDraw() {
              cleanup();
              return true;
          }
      };
  
  }
  