Adding new CHANGING transition to LayoutTransition.

LayoutTransition used to depend on child views being added/removed or
shown/hidden in the transition container. These evens would trigger animations
to fade the child view as well as those to animate the side-affected changes
to sibling views. This CL enables a new feature in LayoutTransition that
enables animating any changes to the layout of the children in the container
whenever a layout occurs. For example, you can change the LayoutParams of a
child view and call requestLayout() to automatically animate those changes.

This capability is not enabled by default. To enable, call the new
LayoutTransition.enableTransitionType(LayoutTransition.CHANGING) method.

Change-Id: I4d07a3b36245353b2151f0dca4f75080ab6a4592
diff --git a/api/current.txt b/api/current.txt
index da28937..7698924 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2311,6 +2311,8 @@
     ctor public LayoutTransition();
     method public void addChild(android.view.ViewGroup, android.view.View);
     method public void addTransitionListener(android.animation.LayoutTransition.TransitionListener);
+    method public void disableTransitionType(int);
+    method public void enableTransitionType(int);
     method public android.animation.Animator getAnimator(int);
     method public long getDuration(int);
     method public android.animation.TimeInterpolator getInterpolator(int);
@@ -2321,6 +2323,7 @@
     method public void hideChild(android.view.ViewGroup, android.view.View, int);
     method public boolean isChangingLayout();
     method public boolean isRunning();
+    method public boolean isTransitionTypeEnabled(int);
     method public void removeChild(android.view.ViewGroup, android.view.View);
     method public void removeTransitionListener(android.animation.LayoutTransition.TransitionListener);
     method public void setAnimateParentHierarchy(boolean);
@@ -2335,6 +2338,7 @@
     field public static final int APPEARING = 2; // 0x2
     field public static final int CHANGE_APPEARING = 0; // 0x0
     field public static final int CHANGE_DISAPPEARING = 1; // 0x1
+    field public static final int CHANGING = 4; // 0x4
     field public static final int DISAPPEARING = 3; // 0x3
   }
 
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 634e4d8..871bb2c 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -119,6 +119,24 @@
     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().
@@ -127,11 +145,13 @@
     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;
@@ -143,15 +163,16 @@
     private static long DEFAULT_DURATION = 300;
 
     /**
-     * The durations of the four different animations
+     * 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 four different animations. Note that the default behavior of
+     * 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.
@@ -160,12 +181,14 @@
     private long mDisappearingDelay = 0;
     private long mChangingAppearingDelay = 0;
     private long mChangingDisappearingDelay = DEFAULT_DURATION;
+    private long mChangingDelay = 0;
 
     /**
-     * The inter-animation delays used on the two changing animations
+     * The inter-animation delays used on the changing animations
      */
     private long mChangingAppearingStagger = 0;
     private long mChangingDisappearingStagger = 0;
+    private long mChangingStagger = 0;
 
     /**
      * The default interpolators used for the animations
@@ -174,6 +197,7 @@
     private TimeInterpolator mDisappearingInterpolator = new AccelerateDecelerateInterpolator();
     private TimeInterpolator mChangingAppearingInterpolator = new DecelerateInterpolator();
     private TimeInterpolator mChangingDisappearingInterpolator = new DecelerateInterpolator();
+    private TimeInterpolator mChangingInterpolator = new DecelerateInterpolator();
 
     /**
      * These hashmaps are used to store the animations that are currently running as part of
@@ -212,6 +236,13 @@
     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.
      */
@@ -248,6 +279,9 @@
             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);
@@ -260,6 +294,7 @@
         }
         mChangingAppearingAnim = defaultChangeIn;
         mChangingDisappearingAnim = defaultChangeOut;
+        mChangingAnim = defaultChange;
         mAppearingAnim = defaultFadeIn;
         mDisappearingAnim = defaultFadeOut;
     }
@@ -275,18 +310,101 @@
     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 #APPEARING}, or {@link #DISAPPEARING}, which 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)
      */
@@ -298,6 +416,9 @@
             case CHANGE_DISAPPEARING:
                 mChangingDisappearingDelay = delay;
                 break;
+            case CHANGING:
+                mChangingDelay = delay;
+                break;
             case APPEARING:
                 mAppearingDelay = delay;
                 break;
@@ -312,9 +433,9 @@
      * <code>transitionType</code> parameter determines the animation whose start delay
      * is returned.
      *
-     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
-     * {@link #APPEARING}, or {@link #DISAPPEARING}, which 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()
      */
@@ -324,6 +445,8 @@
                 return mChangingAppearingDelay;
             case CHANGE_DISAPPEARING:
                 return mChangingDisappearingDelay;
+            case CHANGING:
+                return mChangingDelay;
             case APPEARING:
                 return mAppearingDelay;
             case DISAPPEARING:
@@ -338,9 +461,9 @@
      * <code>transitionType</code> parameter determines the animation whose duration
      * is being set.
      *
-     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
-     * {@link #APPEARING}, or {@link #DISAPPEARING}, which 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)
      */
@@ -352,6 +475,9 @@
             case CHANGE_DISAPPEARING:
                 mChangingDisappearingDuration = duration;
                 break;
+            case CHANGING:
+                mChangingDuration = duration;
+                break;
             case APPEARING:
                 mAppearingDuration = duration;
                 break;
@@ -366,9 +492,9 @@
      * <code>transitionType</code> parameter determines the animation whose duration
      * is returned.
      *
-     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
-     * {@link #APPEARING}, or {@link #DISAPPEARING}, which 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()
      */
@@ -378,6 +504,8 @@
                 return mChangingAppearingDuration;
             case CHANGE_DISAPPEARING:
                 return mChangingDisappearingDuration;
+            case CHANGING:
+                return mChangingDuration;
             case APPEARING:
                 return mAppearingDuration;
             case DISAPPEARING:
@@ -389,9 +517,10 @@
 
     /**
      * Sets the length of time to delay between starting each animation during one of the
-     * CHANGE animations.
+     * change animations.
      *
-     * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}.
+     * @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.
      */
@@ -403,15 +532,19 @@
             case CHANGE_DISAPPEARING:
                 mChangingDisappearingStagger = duration;
                 break;
+            case CHANGING:
+                mChangingStagger = duration;
+                break;
             // noop other cases
         }
     }
 
     /**
-     * Tets the length of time to delay between starting each animation during one of the
-     * CHANGE animations.
+     * 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} or @link #CHANGE_DISAPPEARING}.
+     * @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.
      */
@@ -421,6 +554,8 @@
                 return mChangingAppearingStagger;
             case CHANGE_DISAPPEARING:
                 return mChangingDisappearingStagger;
+            case CHANGING:
+                return mChangingStagger;
         }
         // shouldn't reach here
         return 0;
@@ -431,9 +566,9 @@
      * <code>transitionType</code> parameter determines the animation whose interpolator
      * is being set.
      *
-     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
-     * {@link #APPEARING}, or {@link #DISAPPEARING}, which 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 interpolator is being set.
      * @param interpolator The interpolator that the specified animation should use.
      * @see Animator#setInterpolator(TimeInterpolator)
      */
@@ -445,6 +580,9 @@
             case CHANGE_DISAPPEARING:
                 mChangingDisappearingInterpolator = interpolator;
                 break;
+            case CHANGING:
+                mChangingInterpolator = interpolator;
+                break;
             case APPEARING:
                 mAppearingInterpolator = interpolator;
                 break;
@@ -459,9 +597,9 @@
      * <code>transitionType</code> parameter determines the animation whose interpolator
      * is returned.
      *
-     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
-     * {@link #APPEARING}, or {@link #DISAPPEARING}, which 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 interpolator is being returned.
      * @return TimeInterpolator The interpolator that the specified animation uses.
      * @see Animator#setInterpolator(TimeInterpolator)
      */
@@ -471,6 +609,8 @@
                 return mChangingAppearingInterpolator;
             case CHANGE_DISAPPEARING:
                 return mChangingDisappearingInterpolator;
+            case CHANGING:
+                return mChangingInterpolator;
             case APPEARING:
                 return mAppearingInterpolator;
             case DISAPPEARING:
@@ -504,9 +644,9 @@
      * 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 #APPEARING}, or {@link #DISAPPEARING}, which 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 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.
      */
@@ -518,6 +658,9 @@
             case CHANGE_DISAPPEARING:
                 mChangingDisappearingAnim = animator;
                 break;
+            case CHANGING:
+                mChangingAnim = animator;
+                break;
             case APPEARING:
                 mAppearingAnim = animator;
                 break;
@@ -530,9 +673,9 @@
     /**
      * Gets the animation used during one of the transition types that may run.
      *
-     * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
-     * {@link #APPEARING}, or {@link #DISAPPEARING}, which 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 animator is being returned.
      * @return Animator The animation being used for the given transition type.
      * @see #setAnimator(int, Animator)
      */
@@ -542,6 +685,8 @@
                 return mChangingAppearingAnim;
             case CHANGE_DISAPPEARING:
                 return mChangingDisappearingAnim;
+            case CHANGING:
+                return mChangingAnim;
             case APPEARING:
                 return mAppearingAnim;
             case DISAPPEARING:
@@ -554,20 +699,44 @@
     /**
      * 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 or disappearing) and ask it to populate its start values from its
+     * 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 an appearing or disappearing change.
-     * @param newView The view being added to or removed from the parent.
-     * @param changeReason A value of APPEARING or DISAPPEARING, indicating whether the
-     * transition is occuring because an item is being added to or removed from the parent.
+     * @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 = (changeReason == APPEARING) ?
-                mChangingAppearingAnim : mChangingDisappearingAnim;
+        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;
@@ -575,8 +744,6 @@
 
         // reset the inter-animation delay, in case we use it later
         staggerDelay = 0;
-        final long duration = (changeReason == APPEARING) ?
-                mChangingAppearingDuration : mChangingDisappearingDuration;
 
         final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup
         if (!observer.isAlive()) {
@@ -594,8 +761,6 @@
             }
         }
         if (mAnimateParentHierarchy) {
-            Animator parentAnimator = (changeReason == APPEARING) ?
-                    defaultChangeIn : defaultChangeOut;
             ViewGroup tempParent = parent;
             while (tempParent != null) {
                 ViewParent parentParent = tempParent.getParent();
@@ -727,13 +892,20 @@
                     }
                 }
 
-                long startDelay;
-                if (changeReason == APPEARING) {
-                    startDelay = mChangingAppearingDelay + staggerDelay;
-                    staggerDelay += mChangingAppearingStagger;
-                } else {
-                    startDelay = mChangingDisappearingDelay + staggerDelay;
-                    staggerDelay += mChangingDisappearingStagger;
+                long startDelay = 0;
+                switch (changeReason) {
+                    case APPEARING:
+                        startDelay = mChangingAppearingDelay + staggerDelay;
+                        staggerDelay += mChangingAppearingStagger;
+                        break;
+                    case DISAPPEARING:
+                        startDelay = mChangingDisappearingDelay + staggerDelay;
+                        staggerDelay += mChangingDisappearingStagger;
+                        break;
+                    case CHANGING:
+                        startDelay = mChangingDelay + staggerDelay;
+                        staggerDelay += mChangingStagger;
+                        break;
                 }
                 anim.setStartDelay(startDelay);
                 anim.setDuration(duration);
@@ -766,7 +938,8 @@
                     for (TransitionListener listener : mListeners) {
                         listener.startTransition(LayoutTransition.this, parent, child,
                                 changeReason == APPEARING ?
-                                        CHANGE_APPEARING : CHANGE_DISAPPEARING);
+                                        CHANGE_APPEARING : changeReason == DISAPPEARING ?
+                                        CHANGE_DISAPPEARING : CHANGING);
                     }
                 }
             }
@@ -784,7 +957,8 @@
                     for (TransitionListener listener : mListeners) {
                         listener.endTransition(LayoutTransition.this, parent, child,
                                 changeReason == APPEARING ?
-                                        CHANGE_APPEARING : CHANGE_DISAPPEARING);
+                                        CHANGE_APPEARING : changeReason == DISAPPEARING ?
+                                        CHANGE_DISAPPEARING : CHANGING);
                     }
                 }
             }
@@ -902,6 +1076,7 @@
         switch (transitionType) {
             case CHANGE_APPEARING:
             case CHANGE_DISAPPEARING:
+            case CHANGING:
                 if (currentChangingAnimations.size() > 0) {
                     LinkedHashMap<View, Animator> currentAnimCopy =
                             (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
@@ -1031,21 +1206,48 @@
      * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
      */
     private void addChild(ViewGroup parent, View child, boolean changesLayout) {
-        // Want disappearing animations to finish up before proceeding
-        cancel(DISAPPEARING);
-        if (changesLayout) {
+        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 (mListeners != null) {
+        if (mListeners != null && (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
             for (TransitionListener listener : mListeners) {
                 listener.startTransition(this, parent, child, APPEARING);
             }
         }
-        if (changesLayout) {
+        if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
             runChangeTransition(parent, child, APPEARING);
         }
-        runAppearingTransition(parent, child);
+        if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
+            runAppearingTransition(parent, child);
+        }
+    }
+
+    /**
+     * 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 ((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);
+        }
     }
 
     /**
@@ -1097,21 +1299,28 @@
      * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
      */
     private void removeChild(ViewGroup parent, View child, boolean changesLayout) {
-        // Want appearing animations to finish up before proceeding
-        cancel(APPEARING);
-        if (changesLayout) {
+        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 (mListeners != null) {
+        if (mListeners != null && (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
             for (TransitionListener listener : mListeners) {
                 listener.startTransition(this, parent, child, DISAPPEARING);
             }
         }
-        if (changesLayout) {
+        if (changesLayout &&
+                (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
             runChangeTransition(parent, child, DISAPPEARING);
         }
-        runDisappearingTransition(parent, child);
+        if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
+            runDisappearingTransition(parent, child);
+        }
     }
 
     /**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 0b973e5..6371963 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4207,6 +4207,9 @@
     @Override
     public final void layout(int l, int t, int r, int b) {
         if (mTransition == null || !mTransition.isChangingLayout()) {
+            if (mTransition != null) {
+                mTransition.layoutChange(this);
+            }
             super.layout(l, t, r, b);
         } else {
             // record the fact that we noop'd it; request layout when transition finishes