Home | History | Annotate | Download | only in animation
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.animation;
     18 
     19 import android.view.View;
     20 import android.view.ViewGroup;
     21 import android.view.ViewParent;
     22 import android.view.ViewTreeObserver;
     23 import android.view.animation.AccelerateDecelerateInterpolator;
     24 import android.view.animation.DecelerateInterpolator;
     25 
     26 import java.util.ArrayList;
     27 import java.util.Collection;
     28 import java.util.HashMap;
     29 import java.util.LinkedHashMap;
     30 import java.util.List;
     31 
     32 /**
     33  * This class enables automatic animations on layout changes in ViewGroup objects. To enable
     34  * transitions for a layout container, create a LayoutTransition object and set it on any
     35  * ViewGroup by calling {@link ViewGroup#setLayoutTransition(LayoutTransition)}. This will cause
     36  * default animations to run whenever items are added to or removed from that container. To specify
     37  * custom animations, use the {@link LayoutTransition#setAnimator(int, Animator)
     38  * setAnimator()} method.
     39  *
     40  * <p>One of the core concepts of these transition animations is that there are two types of
     41  * changes that cause the transition and four different animations that run because of
     42  * those changes. The changes that trigger the transition are items being added to a container
     43  * (referred to as an "appearing" transition) or removed from a container (also known as
     44  * "disappearing"). Setting the visibility of views (between GONE and VISIBLE) will trigger
     45  * the same add/remove logic. The animations that run due to those events are one that animates
     46  * items being added, one that animates items being removed, and two that animate the other
     47  * items in the container that change due to the add/remove occurrence. Users of
     48  * the transition may want different animations for the changing items depending on whether
     49  * they are changing due to an appearing or disappearing event, so there is one animation for
     50  * each of these variations of the changing event. Most of the API of this class is concerned
     51  * with setting up the basic properties of the animations used in these four situations,
     52  * or with setting up custom animations for any or all of the four.</p>
     53  *
     54  * <p>By default, the DISAPPEARING animation begins immediately, as does the CHANGE_APPEARING
     55  * animation. The other animations begin after a delay that is set to the default duration
     56  * of the animations. This behavior facilitates a sequence of animations in transitions as
     57  * follows: when an item is being added to a layout, the other children of that container will
     58  * move first (thus creating space for the new item), then the appearing animation will run to
     59  * animate the item being added. Conversely, when an item is removed from a container, the
     60  * animation to remove it will run first, then the animations of the other children in the
     61  * layout will run (closing the gap created in the layout when the item was removed). If this
     62  * default choreography behavior is not desired, the {@link #setDuration(int, long)} and
     63  * {@link #setStartDelay(int, long)} of any or all of the animations can be changed as
     64  * appropriate.</p>
     65  *
     66  * <p>The animations specified for the transition, both the defaults and any custom animations
     67  * set on the transition object, are templates only. That is, these animations exist to hold the
     68  * basic animation properties, such as the duration, start delay, and properties being animated.
     69  * But the actual target object, as well as the start and end values for those properties, are
     70  * set automatically in the process of setting up the transition each time it runs. Each of the
     71  * animations is cloned from the original copy and the clone is then populated with the dynamic
     72  * values of the target being animated (such as one of the items in a layout container that is
     73  * moving as a result of the layout event) as well as the values that are changing (such as the
     74  * position and size of that object). The actual values that are pushed to each animation
     75  * depends on what properties are specified for the animation. For example, the default
     76  * CHANGE_APPEARING animation animates the <code>left</code>, <code>top</code>, <code>right</code>,
     77  * <code>bottom</code>, <code>scrollX</code>, and <code>scrollY</code> properties.
     78  * Values for these properties are updated with the pre- and post-layout
     79  * values when the transition begins. Custom animations will be similarly populated with
     80  * the target and values being animated, assuming they use ObjectAnimator objects with
     81  * property names that are known on the target object.</p>
     82  *
     83  * <p>This class, and the associated XML flag for containers, animateLayoutChanges="true",
     84  * provides a simple utility meant for automating changes in straightforward situations.
     85  * Using LayoutTransition at multiple levels of a nested view hierarchy may not work due to the
     86  * interrelationship of the various levels of layout. Also, a container that is being scrolled
     87  * at the same time as items are being added or removed is probably not a good candidate for
     88  * this utility, because the before/after locations calculated by LayoutTransition
     89  * may not match the actual locations when the animations finish due to the container
     90  * being scrolled as the animations are running. You can work around that
     91  * particular issue by disabling the 'changing' animations by setting the CHANGE_APPEARING
     92  * and CHANGE_DISAPPEARING animations to null, and setting the startDelay of the
     93  * other animations appropriately.</p>
     94  */
     95 public class LayoutTransition {
     96 
     97     /**
     98      * A flag indicating the animation that runs on those items that are changing
     99      * due to a new item appearing in the container.
    100      */
    101     public static final int CHANGE_APPEARING = 0;
    102 
    103     /**
    104      * A flag indicating the animation that runs on those items that are changing
    105      * due to an item disappearing from the container.
    106      */
    107     public static final int CHANGE_DISAPPEARING = 1;
    108 
    109     /**
    110      * A flag indicating the animation that runs on those items that are appearing
    111      * in the container.
    112      */
    113     public static final int APPEARING = 2;
    114 
    115     /**
    116      * A flag indicating the animation that runs on those items that are disappearing
    117      * from the container.
    118      */
    119     public static final int DISAPPEARING = 3;
    120 
    121     /**
    122      * A flag indicating the animation that runs on those items that are changing
    123      * due to a layout change not caused by items being added to or removed
    124      * from the container. This transition type is not enabled by default; it can be
    125      * enabled via {@link #enableTransitionType(int)}.
    126      */
    127     public static final int CHANGING = 4;
    128 
    129     /**
    130      * Private bit fields used to set the collection of enabled transition types for
    131      * mTransitionTypes.
    132      */
    133     private static final int FLAG_APPEARING             = 0x01;
    134     private static final int FLAG_DISAPPEARING          = 0x02;
    135     private static final int FLAG_CHANGE_APPEARING      = 0x04;
    136     private static final int FLAG_CHANGE_DISAPPEARING   = 0x08;
    137     private static final int FLAG_CHANGING              = 0x10;
    138 
    139     /**
    140      * These variables hold the animations that are currently used to run the transition effects.
    141      * These animations are set to defaults, but can be changed to custom animations by
    142      * calls to setAnimator().
    143      */
    144     private Animator mDisappearingAnim = null;
    145     private Animator mAppearingAnim = null;
    146     private Animator mChangingAppearingAnim = null;
    147     private Animator mChangingDisappearingAnim = null;
    148     private Animator mChangingAnim = null;
    149 
    150     /**
    151      * These are the default animations, defined in the constructor, that will be used
    152      * unless the user specifies custom animations.
    153      */
    154     private static ObjectAnimator defaultChange;
    155     private static ObjectAnimator defaultChangeIn;
    156     private static ObjectAnimator defaultChangeOut;
    157     private static ObjectAnimator defaultFadeIn;
    158     private static ObjectAnimator defaultFadeOut;
    159 
    160     /**
    161      * The default duration used by all animations.
    162      */
    163     private static long DEFAULT_DURATION = 300;
    164 
    165     /**
    166      * The durations of the different animations
    167      */
    168     private long mChangingAppearingDuration = DEFAULT_DURATION;
    169     private long mChangingDisappearingDuration = DEFAULT_DURATION;
    170     private long mChangingDuration = DEFAULT_DURATION;
    171     private long mAppearingDuration = DEFAULT_DURATION;
    172     private long mDisappearingDuration = DEFAULT_DURATION;
    173 
    174     /**
    175      * The start delays of the different animations. Note that the default behavior of
    176      * the appearing item is the default duration, since it should wait for the items to move
    177      * before fading it. Same for the changing animation when disappearing; it waits for the item
    178      * to fade out before moving the other items.
    179      */
    180     private long mAppearingDelay = DEFAULT_DURATION;
    181     private long mDisappearingDelay = 0;
    182     private long mChangingAppearingDelay = 0;
    183     private long mChangingDisappearingDelay = DEFAULT_DURATION;
    184     private long mChangingDelay = 0;
    185 
    186     /**
    187      * The inter-animation delays used on the changing animations
    188      */
    189     private long mChangingAppearingStagger = 0;
    190     private long mChangingDisappearingStagger = 0;
    191     private long mChangingStagger = 0;
    192 
    193     /**
    194      * Static interpolators - these are stateless and can be shared across the instances
    195      */
    196     private static TimeInterpolator ACCEL_DECEL_INTERPOLATOR =
    197             new AccelerateDecelerateInterpolator();
    198     private static TimeInterpolator DECEL_INTERPOLATOR = new DecelerateInterpolator();
    199     private static TimeInterpolator sAppearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
    200     private static TimeInterpolator sDisappearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
    201     private static TimeInterpolator sChangingAppearingInterpolator = DECEL_INTERPOLATOR;
    202     private static TimeInterpolator sChangingDisappearingInterpolator = DECEL_INTERPOLATOR;
    203     private static TimeInterpolator sChangingInterpolator = DECEL_INTERPOLATOR;
    204 
    205     /**
    206      * The default interpolators used for the animations
    207      */
    208     private TimeInterpolator mAppearingInterpolator = sAppearingInterpolator;
    209     private TimeInterpolator mDisappearingInterpolator = sDisappearingInterpolator;
    210     private TimeInterpolator mChangingAppearingInterpolator = sChangingAppearingInterpolator;
    211     private TimeInterpolator mChangingDisappearingInterpolator = sChangingDisappearingInterpolator;
    212     private TimeInterpolator mChangingInterpolator = sChangingInterpolator;
    213 
    214     /**
    215      * These hashmaps are used to store the animations that are currently running as part of
    216      * the transition. The reason for this is that a further layout event should cause
    217      * existing animations to stop where they are prior to starting new animations. So
    218      * we cache all of the current animations in this map for possible cancellation on
    219      * another layout event. LinkedHashMaps are used to preserve the order in which animations
    220      * are inserted, so that we process events (such as setting up start values) in the same order.
    221      */
    222     private final HashMap<View, Animator> pendingAnimations =
    223             new HashMap<View, Animator>();
    224     private final LinkedHashMap<View, Animator> currentChangingAnimations =
    225             new LinkedHashMap<View, Animator>();
    226     private final LinkedHashMap<View, Animator> currentAppearingAnimations =
    227             new LinkedHashMap<View, Animator>();
    228     private final LinkedHashMap<View, Animator> currentDisappearingAnimations =
    229             new LinkedHashMap<View, Animator>();
    230 
    231     /**
    232      * This hashmap is used to track the listeners that have been added to the children of
    233      * a container. When a layout change occurs, an animation is created for each View, so that
    234      * the pre-layout values can be cached in that animation. Then a listener is added to the
    235      * view to see whether the layout changes the bounds of that view. If so, the animation
    236      * is set with the final values and then run. If not, the animation is not started. When
    237      * the process of setting up and running all appropriate animations is done, we need to
    238      * remove these listeners and clear out the map.
    239      */
    240     private final HashMap<View, View.OnLayoutChangeListener> layoutChangeListenerMap =
    241             new HashMap<View, View.OnLayoutChangeListener>();
    242 
    243     /**
    244      * Used to track the current delay being assigned to successive animations as they are
    245      * started. This value is incremented for each new animation, then zeroed before the next
    246      * transition begins.
    247      */
    248     private long staggerDelay;
    249 
    250     /**
    251      * These are the types of transition animations that the LayoutTransition is reacting
    252      * to. By default, appearing/disappearing and the change animations related to them are
    253      * enabled (not CHANGING).
    254      */
    255     private int mTransitionTypes = FLAG_CHANGE_APPEARING | FLAG_CHANGE_DISAPPEARING |
    256             FLAG_APPEARING | FLAG_DISAPPEARING;
    257     /**
    258      * The set of listeners that should be notified when APPEARING/DISAPPEARING transitions
    259      * start and end.
    260      */
    261     private ArrayList<TransitionListener> mListeners;
    262 
    263     /**
    264      * Controls whether changing animations automatically animate the parent hierarchy as well.
    265      * This behavior prevents artifacts when wrap_content layouts snap to the end state as the
    266      * transition begins, causing visual glitches and clipping.
    267      * Default value is true.
    268      */
    269     private boolean mAnimateParentHierarchy = true;
    270 
    271 
    272     /**
    273      * Constructs a LayoutTransition object. By default, the object will listen to layout
    274      * events on any ViewGroup that it is set on and will run default animations for each
    275      * type of layout event.
    276      */
    277     public LayoutTransition() {
    278         if (defaultChangeIn == null) {
    279             // "left" is just a placeholder; we'll put real properties/values in when needed
    280             PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
    281             PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
    282             PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
    283             PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
    284             PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
    285             PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
    286             defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null,
    287                     pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
    288             defaultChangeIn.setDuration(DEFAULT_DURATION);
    289             defaultChangeIn.setStartDelay(mChangingAppearingDelay);
    290             defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
    291             defaultChangeOut = defaultChangeIn.clone();
    292             defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
    293             defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
    294             defaultChange = defaultChangeIn.clone();
    295             defaultChange.setStartDelay(mChangingDelay);
    296             defaultChange.setInterpolator(mChangingInterpolator);
    297 
    298             defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
    299             defaultFadeIn.setDuration(DEFAULT_DURATION);
    300             defaultFadeIn.setStartDelay(mAppearingDelay);
    301             defaultFadeIn.setInterpolator(mAppearingInterpolator);
    302             defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
    303             defaultFadeOut.setDuration(DEFAULT_DURATION);
    304             defaultFadeOut.setStartDelay(mDisappearingDelay);
    305             defaultFadeOut.setInterpolator(mDisappearingInterpolator);
    306         }
    307         mChangingAppearingAnim = defaultChangeIn;
    308         mChangingDisappearingAnim = defaultChangeOut;
    309         mChangingAnim = defaultChange;
    310         mAppearingAnim = defaultFadeIn;
    311         mDisappearingAnim = defaultFadeOut;
    312     }
    313 
    314     /**
    315      * Sets the duration to be used by all animations of this transition object. If you want to
    316      * set the duration of just one of the animations in particular, use the
    317      * {@link #setDuration(int, long)} method.
    318      *
    319      * @param duration The length of time, in milliseconds, that the transition animations
    320      * should last.
    321      */
    322     public void setDuration(long duration) {
    323         mChangingAppearingDuration = duration;
    324         mChangingDisappearingDuration = duration;
    325         mChangingDuration = duration;
    326         mAppearingDuration = duration;
    327         mDisappearingDuration = duration;
    328     }
    329 
    330     /**
    331      * Enables the specified transitionType for this LayoutTransition object.
    332      * By default, a LayoutTransition listens for changes in children being
    333      * added/remove/hidden/shown in the container, and runs the animations associated with
    334      * those events. That is, all transition types besides {@link #CHANGING} are enabled by default.
    335      * You can also enable {@link #CHANGING} animations by calling this method with the
    336      * {@link #CHANGING} transitionType.
    337      *
    338      * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    339      * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
    340      */
    341     public void enableTransitionType(int transitionType) {
    342         switch (transitionType) {
    343             case APPEARING:
    344                 mTransitionTypes |= FLAG_APPEARING;
    345                 break;
    346             case DISAPPEARING:
    347                 mTransitionTypes |= FLAG_DISAPPEARING;
    348                 break;
    349             case CHANGE_APPEARING:
    350                 mTransitionTypes |= FLAG_CHANGE_APPEARING;
    351                 break;
    352             case CHANGE_DISAPPEARING:
    353                 mTransitionTypes |= FLAG_CHANGE_DISAPPEARING;
    354                 break;
    355             case CHANGING:
    356                 mTransitionTypes |= FLAG_CHANGING;
    357                 break;
    358         }
    359     }
    360 
    361     /**
    362      * Disables the specified transitionType for this LayoutTransition object.
    363      * By default, all transition types except {@link #CHANGING} are enabled.
    364      *
    365      * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    366      * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
    367      */
    368     public void disableTransitionType(int transitionType) {
    369         switch (transitionType) {
    370             case APPEARING:
    371                 mTransitionTypes &= ~FLAG_APPEARING;
    372                 break;
    373             case DISAPPEARING:
    374                 mTransitionTypes &= ~FLAG_DISAPPEARING;
    375                 break;
    376             case CHANGE_APPEARING:
    377                 mTransitionTypes &= ~FLAG_CHANGE_APPEARING;
    378                 break;
    379             case CHANGE_DISAPPEARING:
    380                 mTransitionTypes &= ~FLAG_CHANGE_DISAPPEARING;
    381                 break;
    382             case CHANGING:
    383                 mTransitionTypes &= ~FLAG_CHANGING;
    384                 break;
    385         }
    386     }
    387 
    388     /**
    389      * Returns whether the specified transitionType is enabled for this LayoutTransition object.
    390      * By default, all transition types except {@link #CHANGING} are enabled.
    391      *
    392      * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    393      * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
    394      * @return true if the specified transitionType is currently enabled, false otherwise.
    395      */
    396     public boolean isTransitionTypeEnabled(int transitionType) {
    397         switch (transitionType) {
    398             case APPEARING:
    399                 return (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING;
    400             case DISAPPEARING:
    401                 return (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING;
    402             case CHANGE_APPEARING:
    403                 return (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING;
    404             case CHANGE_DISAPPEARING:
    405                 return (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING;
    406             case CHANGING:
    407                 return (mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING;
    408         }
    409         return false;
    410     }
    411 
    412     /**
    413      * Sets the start delay on one of the animation objects used by this transition. The
    414      * <code>transitionType</code> parameter determines the animation whose start delay
    415      * is being set.
    416      *
    417      * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    418      * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
    419      * the animation whose start delay is being set.
    420      * @param delay The length of time, in milliseconds, to delay before starting the animation.
    421      * @see Animator#setStartDelay(long)
    422      */
    423     public void setStartDelay(int transitionType, long delay) {
    424         switch (transitionType) {
    425             case CHANGE_APPEARING:
    426                 mChangingAppearingDelay = delay;
    427                 break;
    428             case CHANGE_DISAPPEARING:
    429                 mChangingDisappearingDelay = delay;
    430                 break;
    431             case CHANGING:
    432                 mChangingDelay = delay;
    433                 break;
    434             case APPEARING:
    435                 mAppearingDelay = delay;
    436                 break;
    437             case DISAPPEARING:
    438                 mDisappearingDelay = delay;
    439                 break;
    440         }
    441     }
    442 
    443     /**
    444      * Gets the start delay on one of the animation objects used by this transition. The
    445      * <code>transitionType</code> parameter determines the animation whose start delay
    446      * is returned.
    447      *
    448      * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    449      * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
    450      * the animation whose start delay is returned.
    451      * @return long The start delay of the specified animation.
    452      * @see Animator#getStartDelay()
    453      */
    454     public long getStartDelay(int transitionType) {
    455         switch (transitionType) {
    456             case CHANGE_APPEARING:
    457                 return mChangingAppearingDelay;
    458             case CHANGE_DISAPPEARING:
    459                 return mChangingDisappearingDelay;
    460             case CHANGING:
    461                 return mChangingDelay;
    462             case APPEARING:
    463                 return mAppearingDelay;
    464             case DISAPPEARING:
    465                 return mDisappearingDelay;
    466         }
    467         // shouldn't reach here
    468         return 0;
    469     }
    470 
    471     /**
    472      * Sets the duration on one of the animation objects used by this transition. The
    473      * <code>transitionType</code> parameter determines the animation whose duration
    474      * is being set.
    475      *
    476      * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    477      * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
    478      * the animation whose duration is being set.
    479      * @param duration The length of time, in milliseconds, that the specified animation should run.
    480      * @see Animator#setDuration(long)
    481      */
    482     public void setDuration(int transitionType, long duration) {
    483         switch (transitionType) {
    484             case CHANGE_APPEARING:
    485                 mChangingAppearingDuration = duration;
    486                 break;
    487             case CHANGE_DISAPPEARING:
    488                 mChangingDisappearingDuration = duration;
    489                 break;
    490             case CHANGING:
    491                 mChangingDuration = duration;
    492                 break;
    493             case APPEARING:
    494                 mAppearingDuration = duration;
    495                 break;
    496             case DISAPPEARING:
    497                 mDisappearingDuration = duration;
    498                 break;
    499         }
    500     }
    501 
    502     /**
    503      * Gets the duration on one of the animation objects used by this transition. The
    504      * <code>transitionType</code> parameter determines the animation whose duration
    505      * is returned.
    506      *
    507      * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    508      * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
    509      * the animation whose duration is returned.
    510      * @return long The duration of the specified animation.
    511      * @see Animator#getDuration()
    512      */
    513     public long getDuration(int transitionType) {
    514         switch (transitionType) {
    515             case CHANGE_APPEARING:
    516                 return mChangingAppearingDuration;
    517             case CHANGE_DISAPPEARING:
    518                 return mChangingDisappearingDuration;
    519             case CHANGING:
    520                 return mChangingDuration;
    521             case APPEARING:
    522                 return mAppearingDuration;
    523             case DISAPPEARING:
    524                 return mDisappearingDuration;
    525         }
    526         // shouldn't reach here
    527         return 0;
    528     }
    529 
    530     /**
    531      * Sets the length of time to delay between starting each animation during one of the
    532      * change animations.
    533      *
    534      * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or
    535      * {@link #CHANGING}.
    536      * @param duration The length of time, in milliseconds, to delay before launching the next
    537      * animation in the sequence.
    538      */
    539     public void setStagger(int transitionType, long duration) {
    540         switch (transitionType) {
    541             case CHANGE_APPEARING:
    542                 mChangingAppearingStagger = duration;
    543                 break;
    544             case CHANGE_DISAPPEARING:
    545                 mChangingDisappearingStagger = duration;
    546                 break;
    547             case CHANGING:
    548                 mChangingStagger = duration;
    549                 break;
    550             // noop other cases
    551         }
    552     }
    553 
    554     /**
    555      * Gets the length of time to delay between starting each animation during one of the
    556      * change animations.
    557      *
    558      * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or
    559      * {@link #CHANGING}.
    560      * @return long The length of time, in milliseconds, to delay before launching the next
    561      * animation in the sequence.
    562      */
    563     public long getStagger(int transitionType) {
    564         switch (transitionType) {
    565             case CHANGE_APPEARING:
    566                 return mChangingAppearingStagger;
    567             case CHANGE_DISAPPEARING:
    568                 return mChangingDisappearingStagger;
    569             case CHANGING:
    570                 return mChangingStagger;
    571         }
    572         // shouldn't reach here
    573         return 0;
    574     }
    575 
    576     /**
    577      * Sets the interpolator on one of the animation objects used by this transition. The
    578      * <code>transitionType</code> parameter determines the animation whose interpolator
    579      * is being set.
    580      *
    581      * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    582      * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
    583      * the animation whose interpolator is being set.
    584      * @param interpolator The interpolator that the specified animation should use.
    585      * @see Animator#setInterpolator(TimeInterpolator)
    586      */
    587     public void setInterpolator(int transitionType, TimeInterpolator interpolator) {
    588         switch (transitionType) {
    589             case CHANGE_APPEARING:
    590                 mChangingAppearingInterpolator = interpolator;
    591                 break;
    592             case CHANGE_DISAPPEARING:
    593                 mChangingDisappearingInterpolator = interpolator;
    594                 break;
    595             case CHANGING:
    596                 mChangingInterpolator = interpolator;
    597                 break;
    598             case APPEARING:
    599                 mAppearingInterpolator = interpolator;
    600                 break;
    601             case DISAPPEARING:
    602                 mDisappearingInterpolator = interpolator;
    603                 break;
    604         }
    605     }
    606 
    607     /**
    608      * Gets the interpolator on one of the animation objects used by this transition. The
    609      * <code>transitionType</code> parameter determines the animation whose interpolator
    610      * is returned.
    611      *
    612      * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    613      * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
    614      * the animation whose interpolator is being returned.
    615      * @return TimeInterpolator The interpolator that the specified animation uses.
    616      * @see Animator#setInterpolator(TimeInterpolator)
    617      */
    618     public TimeInterpolator getInterpolator(int transitionType) {
    619         switch (transitionType) {
    620             case CHANGE_APPEARING:
    621                 return mChangingAppearingInterpolator;
    622             case CHANGE_DISAPPEARING:
    623                 return mChangingDisappearingInterpolator;
    624             case CHANGING:
    625                 return mChangingInterpolator;
    626             case APPEARING:
    627                 return mAppearingInterpolator;
    628             case DISAPPEARING:
    629                 return mDisappearingInterpolator;
    630         }
    631         // shouldn't reach here
    632         return null;
    633     }
    634 
    635     /**
    636      * Sets the animation used during one of the transition types that may run. Any
    637      * Animator object can be used, but to be most useful in the context of layout
    638      * transitions, the animation should either be a ObjectAnimator or a AnimatorSet
    639      * of animations including PropertyAnimators. Also, these ObjectAnimator objects
    640      * should be able to get and set values on their target objects automatically. For
    641      * example, a ObjectAnimator that animates the property "left" is able to set and get the
    642      * <code>left</code> property from the View objects being animated by the layout
    643      * transition. The transition works by setting target objects and properties
    644      * dynamically, according to the pre- and post-layoout values of those objects, so
    645      * having animations that can handle those properties appropriately will work best
    646      * for custom animation. The dynamic setting of values is only the case for the
    647      * CHANGE animations; the APPEARING and DISAPPEARING animations are simply run with
    648      * the values they have.
    649      *
    650      * <p>It is also worth noting that any and all animations (and their underlying
    651      * PropertyValuesHolder objects) will have their start and end values set according
    652      * to the pre- and post-layout values. So, for example, a custom animation on "alpha"
    653      * as the CHANGE_APPEARING animation will inherit the real value of alpha on the target
    654      * object (presumably 1) as its starting and ending value when the animation begins.
    655      * Animations which need to use values at the beginning and end that may not match the
    656      * values queried when the transition begins may need to use a different mechanism
    657      * than a standard ObjectAnimator object.</p>
    658      *
    659      * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    660      * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the
    661      * animation whose animator is being set.
    662      * @param animator The animation being assigned. A value of <code>null</code> means that no
    663      * animation will be run for the specified transitionType.
    664      */
    665     public void setAnimator(int transitionType, Animator animator) {
    666         switch (transitionType) {
    667             case CHANGE_APPEARING:
    668                 mChangingAppearingAnim = animator;
    669                 break;
    670             case CHANGE_DISAPPEARING:
    671                 mChangingDisappearingAnim = animator;
    672                 break;
    673             case CHANGING:
    674                 mChangingAnim = animator;
    675                 break;
    676             case APPEARING:
    677                 mAppearingAnim = animator;
    678                 break;
    679             case DISAPPEARING:
    680                 mDisappearingAnim = animator;
    681                 break;
    682         }
    683     }
    684 
    685     /**
    686      * Gets the animation used during one of the transition types that may run.
    687      *
    688      * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    689      * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
    690      * the animation whose animator is being returned.
    691      * @return Animator The animation being used for the given transition type.
    692      * @see #setAnimator(int, Animator)
    693      */
    694     public Animator getAnimator(int transitionType) {
    695         switch (transitionType) {
    696             case CHANGE_APPEARING:
    697                 return mChangingAppearingAnim;
    698             case CHANGE_DISAPPEARING:
    699                 return mChangingDisappearingAnim;
    700             case CHANGING:
    701                 return mChangingAnim;
    702             case APPEARING:
    703                 return mAppearingAnim;
    704             case DISAPPEARING:
    705                 return mDisappearingAnim;
    706         }
    707         // shouldn't reach here
    708         return null;
    709     }
    710 
    711     /**
    712      * This function sets up animations on all of the views that change during layout.
    713      * For every child in the parent, we create a change animation of the appropriate
    714      * type (appearing, disappearing, or changing) and ask it to populate its start values from its
    715      * target view. We add layout listeners to all child views and listen for changes. For
    716      * those views that change, we populate the end values for those animations and start them.
    717      * Animations are not run on unchanging views.
    718      *
    719      * @param parent The container which is undergoing a change.
    720      * @param newView The view being added to or removed from the parent. May be null if the
    721      * changeReason is CHANGING.
    722      * @param changeReason A value of APPEARING, DISAPPEARING, or CHANGING, indicating whether the
    723      * transition is occurring because an item is being added to or removed from the parent, or
    724      * if it is running in response to a layout operation (that is, if the value is CHANGING).
    725      */
    726     private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) {
    727 
    728         Animator baseAnimator = null;
    729         Animator parentAnimator = null;
    730         final long duration;
    731         switch (changeReason) {
    732             case APPEARING:
    733                 baseAnimator = mChangingAppearingAnim;
    734                 duration = mChangingAppearingDuration;
    735                 parentAnimator = defaultChangeIn;
    736                 break;
    737             case DISAPPEARING:
    738                 baseAnimator = mChangingDisappearingAnim;
    739                 duration = mChangingDisappearingDuration;
    740                 parentAnimator = defaultChangeOut;
    741                 break;
    742             case CHANGING:
    743                 baseAnimator = mChangingAnim;
    744                 duration = mChangingDuration;
    745                 parentAnimator = defaultChange;
    746                 break;
    747             default:
    748                 // Shouldn't reach here
    749                 duration = 0;
    750                 break;
    751         }
    752         // If the animation is null, there's nothing to do
    753         if (baseAnimator == null) {
    754             return;
    755         }
    756 
    757         // reset the inter-animation delay, in case we use it later
    758         staggerDelay = 0;
    759 
    760         final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup
    761         if (!observer.isAlive()) {
    762             // If the observer's not in a good state, skip the transition
    763             return;
    764         }
    765         int numChildren = parent.getChildCount();
    766 
    767         for (int i = 0; i < numChildren; ++i) {
    768             final View child = parent.getChildAt(i);
    769 
    770             // only animate the views not being added or removed
    771             if (child != newView) {
    772                 setupChangeAnimation(parent, changeReason, baseAnimator, duration, child);
    773             }
    774         }
    775         if (mAnimateParentHierarchy) {
    776             ViewGroup tempParent = parent;
    777             while (tempParent != null) {
    778                 ViewParent parentParent = tempParent.getParent();
    779                 if (parentParent instanceof ViewGroup) {
    780                     setupChangeAnimation((ViewGroup)parentParent, changeReason, parentAnimator,
    781                             duration, tempParent);
    782                     tempParent = (ViewGroup) parentParent;
    783                 } else {
    784                     tempParent = null;
    785                 }
    786 
    787             }
    788         }
    789 
    790         // This is the cleanup step. When we get this rendering event, we know that all of
    791         // the appropriate animations have been set up and run. Now we can clear out the
    792         // layout listeners.
    793         observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    794             public boolean onPreDraw() {
    795                 parent.getViewTreeObserver().removeOnPreDrawListener(this);
    796                 int count = layoutChangeListenerMap.size();
    797                 if (count > 0) {
    798                     Collection<View> views = layoutChangeListenerMap.keySet();
    799                     for (View view : views) {
    800                         View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view);
    801                         view.removeOnLayoutChangeListener(listener);
    802                     }
    803                 }
    804                 layoutChangeListenerMap.clear();
    805                 return true;
    806             }
    807         });
    808     }
    809 
    810     /**
    811      * This flag controls whether CHANGE_APPEARING or CHANGE_DISAPPEARING animations will
    812      * cause the default changing animation to be run on the parent hierarchy as well. This allows
    813      * containers of transitioning views to also transition, which may be necessary in situations
    814      * where the containers bounds change between the before/after states and may clip their
    815      * children during the transition animations. For example, layouts with wrap_content will
    816      * adjust their bounds according to the dimensions of their children.
    817      *
    818      * <p>The default changing transitions animate the bounds and scroll positions of the
    819      * target views. These are the animations that will run on the parent hierarchy, not
    820      * the custom animations that happen to be set on the transition. This allows custom
    821      * behavior for the children of the transitioning container, but uses standard behavior
    822      * of resizing/rescrolling on any changing parents.
    823      *
    824      * @param animateParentHierarchy A boolean value indicating whether the parents of
    825      * transitioning views should also be animated during the transition. Default value is true.
    826      */
    827     public void setAnimateParentHierarchy(boolean animateParentHierarchy) {
    828         mAnimateParentHierarchy = animateParentHierarchy;
    829     }
    830 
    831     /**
    832      * Utility function called by runChangingTransition for both the children and the parent
    833      * hierarchy.
    834      */
    835     private void setupChangeAnimation(final ViewGroup parent, final int changeReason,
    836             Animator baseAnimator, final long duration, final View child) {
    837 
    838         // If we already have a listener for this child, then we've already set up the
    839         // changing animation we need. Multiple calls for a child may occur when several
    840         // add/remove operations are run at once on a container; each one will trigger
    841         // changes for the existing children in the container.
    842         if (layoutChangeListenerMap.get(child) != null) {
    843             return;
    844         }
    845 
    846         // Don't animate items up from size(0,0); this is likely because the objects
    847         // were offscreen/invisible or otherwise measured to be infinitely small. We don't
    848         // want to see them animate into their real size; just ignore animation requests
    849         // on these views
    850         if (child.getWidth() == 0 && child.getHeight() == 0) {
    851             return;
    852         }
    853 
    854         // Make a copy of the appropriate animation
    855         final Animator anim = baseAnimator.clone();
    856 
    857         // Set the target object for the animation
    858         anim.setTarget(child);
    859 
    860         // A ObjectAnimator (or AnimatorSet of them) can extract start values from
    861         // its target object
    862         anim.setupStartValues();
    863 
    864         // If there's an animation running on this view already, cancel it
    865         Animator currentAnimation = pendingAnimations.get(child);
    866         if (currentAnimation != null) {
    867             currentAnimation.cancel();
    868             pendingAnimations.remove(child);
    869         }
    870         // Cache the animation in case we need to cancel it later
    871         pendingAnimations.put(child, anim);
    872 
    873         // For the animations which don't get started, we have to have a means of
    874         // removing them from the cache, lest we leak them and their target objects.
    875         // We run an animator for the default duration+100 (an arbitrary time, but one
    876         // which should far surpass the delay between setting them up here and
    877         // handling layout events which start them.
    878         ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
    879                 setDuration(duration + 100);
    880         pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
    881             @Override
    882             public void onAnimationEnd(Animator animation) {
    883                 pendingAnimations.remove(child);
    884             }
    885         });
    886         pendingAnimRemover.start();
    887 
    888         // Add a listener to track layout changes on this view. If we don't get a callback,
    889         // then there's nothing to animate.
    890         final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
    891             public void onLayoutChange(View v, int left, int top, int right, int bottom,
    892                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
    893 
    894                 // Tell the animation to extract end values from the changed object
    895                 anim.setupEndValues();
    896                 if (anim instanceof ValueAnimator) {
    897                     boolean valuesDiffer = false;
    898                     ValueAnimator valueAnim = (ValueAnimator)anim;
    899                     PropertyValuesHolder[] oldValues = valueAnim.getValues();
    900                     for (int i = 0; i < oldValues.length; ++i) {
    901                         PropertyValuesHolder pvh = oldValues[i];
    902                         if (pvh.mKeyframes instanceof KeyframeSet) {
    903                             KeyframeSet keyframeSet = (KeyframeSet) pvh.mKeyframes;
    904                             if (keyframeSet.mFirstKeyframe == null ||
    905                                     keyframeSet.mLastKeyframe == null ||
    906                                     !keyframeSet.mFirstKeyframe.getValue().equals(
    907                                             keyframeSet.mLastKeyframe.getValue())) {
    908                                 valuesDiffer = true;
    909                             }
    910                         } else if (!pvh.mKeyframes.getValue(0).equals(pvh.mKeyframes.getValue(1))) {
    911                             valuesDiffer = true;
    912                         }
    913                     }
    914                     if (!valuesDiffer) {
    915                         return;
    916                     }
    917                 }
    918 
    919                 long startDelay = 0;
    920                 switch (changeReason) {
    921                     case APPEARING:
    922                         startDelay = mChangingAppearingDelay + staggerDelay;
    923                         staggerDelay += mChangingAppearingStagger;
    924                         if (mChangingAppearingInterpolator != sChangingAppearingInterpolator) {
    925                             anim.setInterpolator(mChangingAppearingInterpolator);
    926                         }
    927                         break;
    928                     case DISAPPEARING:
    929                         startDelay = mChangingDisappearingDelay + staggerDelay;
    930                         staggerDelay += mChangingDisappearingStagger;
    931                         if (mChangingDisappearingInterpolator !=
    932                                 sChangingDisappearingInterpolator) {
    933                             anim.setInterpolator(mChangingDisappearingInterpolator);
    934                         }
    935                         break;
    936                     case CHANGING:
    937                         startDelay = mChangingDelay + staggerDelay;
    938                         staggerDelay += mChangingStagger;
    939                         if (mChangingInterpolator != sChangingInterpolator) {
    940                             anim.setInterpolator(mChangingInterpolator);
    941                         }
    942                         break;
    943                 }
    944                 anim.setStartDelay(startDelay);
    945                 anim.setDuration(duration);
    946 
    947                 Animator prevAnimation = currentChangingAnimations.get(child);
    948                 if (prevAnimation != null) {
    949                     prevAnimation.cancel();
    950                 }
    951                 Animator pendingAnimation = pendingAnimations.get(child);
    952                 if (pendingAnimation != null) {
    953                     pendingAnimations.remove(child);
    954                 }
    955                 // Cache the animation in case we need to cancel it later
    956                 currentChangingAnimations.put(child, anim);
    957 
    958                 parent.requestTransitionStart(LayoutTransition.this);
    959 
    960                 // this only removes listeners whose views changed - must clear the
    961                 // other listeners later
    962                 child.removeOnLayoutChangeListener(this);
    963                 layoutChangeListenerMap.remove(child);
    964             }
    965         };
    966         // Remove the animation from the cache when it ends
    967         anim.addListener(new AnimatorListenerAdapter() {
    968 
    969             @Override
    970             public void onAnimationStart(Animator animator) {
    971                 if (hasListeners()) {
    972                     ArrayList<TransitionListener> listeners =
    973                             (ArrayList<TransitionListener>) mListeners.clone();
    974                     for (TransitionListener listener : listeners) {
    975                         listener.startTransition(LayoutTransition.this, parent, child,
    976                                 changeReason == APPEARING ?
    977                                         CHANGE_APPEARING : changeReason == DISAPPEARING ?
    978                                         CHANGE_DISAPPEARING : CHANGING);
    979                     }
    980                 }
    981             }
    982 
    983             @Override
    984             public void onAnimationCancel(Animator animator) {
    985                 child.removeOnLayoutChangeListener(listener);
    986                 layoutChangeListenerMap.remove(child);
    987             }
    988 
    989             @Override
    990             public void onAnimationEnd(Animator animator) {
    991                 currentChangingAnimations.remove(child);
    992                 if (hasListeners()) {
    993                     ArrayList<TransitionListener> listeners =
    994                             (ArrayList<TransitionListener>) mListeners.clone();
    995                     for (TransitionListener listener : listeners) {
    996                         listener.endTransition(LayoutTransition.this, parent, child,
    997                                 changeReason == APPEARING ?
    998                                         CHANGE_APPEARING : changeReason == DISAPPEARING ?
    999                                         CHANGE_DISAPPEARING : CHANGING);
   1000                     }
   1001                 }
   1002             }
   1003         });
   1004 
   1005         child.addOnLayoutChangeListener(listener);
   1006         // cache the listener for later removal
   1007         layoutChangeListenerMap.put(child, listener);
   1008     }
   1009 
   1010     /**
   1011      * Starts the animations set up for a CHANGING transition. We separate the setup of these
   1012      * animations from actually starting them, to avoid side-effects that starting the animations
   1013      * may have on the properties of the affected objects. After setup, we tell the affected parent
   1014      * that this transition should be started. The parent informs its ViewAncestor, which then
   1015      * starts the transition after the current layout/measurement phase, just prior to drawing
   1016      * the view hierarchy.
   1017      *
   1018      * @hide
   1019      */
   1020     public void startChangingAnimations() {
   1021         LinkedHashMap<View, Animator> currentAnimCopy =
   1022                 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
   1023         for (Animator anim : currentAnimCopy.values()) {
   1024             if (anim instanceof ObjectAnimator) {
   1025                 ((ObjectAnimator) anim).setCurrentPlayTime(0);
   1026             }
   1027             anim.start();
   1028         }
   1029     }
   1030 
   1031     /**
   1032      * Ends the animations that are set up for a CHANGING transition. This is a variant of
   1033      * startChangingAnimations() which is called when the window the transition is playing in
   1034      * is not visible. We need to make sure the animations put their targets in their end states
   1035      * and that the transition finishes to remove any mid-process state (such as isRunning()).
   1036      *
   1037      * @hide
   1038      */
   1039     public void endChangingAnimations() {
   1040         LinkedHashMap<View, Animator> currentAnimCopy =
   1041                 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
   1042         for (Animator anim : currentAnimCopy.values()) {
   1043             anim.start();
   1044             anim.end();
   1045         }
   1046         // listeners should clean up the currentChangingAnimations list, but just in case...
   1047         currentChangingAnimations.clear();
   1048     }
   1049 
   1050     /**
   1051      * Returns true if animations are running which animate layout-related properties. This
   1052      * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
   1053      * are running, since these animations operate on layout-related properties.
   1054      *
   1055      * @return true if CHANGE_APPEARING or CHANGE_DISAPPEARING animations are currently
   1056      * running.
   1057      */
   1058     public boolean isChangingLayout() {
   1059         return (currentChangingAnimations.size() > 0);
   1060     }
   1061 
   1062     /**
   1063      * Returns true if any of the animations in this transition are currently running.
   1064      *
   1065      * @return true if any animations in the transition are running.
   1066      */
   1067     public boolean isRunning() {
   1068         return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 ||
   1069                 currentDisappearingAnimations.size() > 0);
   1070     }
   1071 
   1072     /**
   1073      * Cancels the currently running transition. Note that we cancel() the changing animations
   1074      * but end() the visibility animations. This is because this method is currently called
   1075      * in the context of starting a new transition, so we want to move things from their mid-
   1076      * transition positions, but we want them to have their end-transition visibility.
   1077      *
   1078      * @hide
   1079      */
   1080     public void cancel() {
   1081         if (currentChangingAnimations.size() > 0) {
   1082             LinkedHashMap<View, Animator> currentAnimCopy =
   1083                     (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
   1084             for (Animator anim : currentAnimCopy.values()) {
   1085                 anim.cancel();
   1086             }
   1087             currentChangingAnimations.clear();
   1088         }
   1089         if (currentAppearingAnimations.size() > 0) {
   1090             LinkedHashMap<View, Animator> currentAnimCopy =
   1091                     (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
   1092             for (Animator anim : currentAnimCopy.values()) {
   1093                 anim.end();
   1094             }
   1095             currentAppearingAnimations.clear();
   1096         }
   1097         if (currentDisappearingAnimations.size() > 0) {
   1098             LinkedHashMap<View, Animator> currentAnimCopy =
   1099                     (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
   1100             for (Animator anim : currentAnimCopy.values()) {
   1101                 anim.end();
   1102             }
   1103             currentDisappearingAnimations.clear();
   1104         }
   1105     }
   1106 
   1107     /**
   1108      * Cancels the specified type of transition. Note that we cancel() the changing animations
   1109      * but end() the visibility animations. This is because this method is currently called
   1110      * in the context of starting a new transition, so we want to move things from their mid-
   1111      * transition positions, but we want them to have their end-transition visibility.
   1112      *
   1113      * @hide
   1114      */
   1115     public void cancel(int transitionType) {
   1116         switch (transitionType) {
   1117             case CHANGE_APPEARING:
   1118             case CHANGE_DISAPPEARING:
   1119             case CHANGING:
   1120                 if (currentChangingAnimations.size() > 0) {
   1121                     LinkedHashMap<View, Animator> currentAnimCopy =
   1122                             (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
   1123                     for (Animator anim : currentAnimCopy.values()) {
   1124                         anim.cancel();
   1125                     }
   1126                     currentChangingAnimations.clear();
   1127                 }
   1128                 break;
   1129             case APPEARING:
   1130                 if (currentAppearingAnimations.size() > 0) {
   1131                     LinkedHashMap<View, Animator> currentAnimCopy =
   1132                             (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
   1133                     for (Animator anim : currentAnimCopy.values()) {
   1134                         anim.end();
   1135                     }
   1136                     currentAppearingAnimations.clear();
   1137                 }
   1138                 break;
   1139             case DISAPPEARING:
   1140                 if (currentDisappearingAnimations.size() > 0) {
   1141                     LinkedHashMap<View, Animator> currentAnimCopy =
   1142                             (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
   1143                     for (Animator anim : currentAnimCopy.values()) {
   1144                         anim.end();
   1145                     }
   1146                     currentDisappearingAnimations.clear();
   1147                 }
   1148                 break;
   1149         }
   1150     }
   1151 
   1152     /**
   1153      * This method runs the animation that makes an added item appear.
   1154      *
   1155      * @param parent The ViewGroup to which the View is being added.
   1156      * @param child The View being added to the ViewGroup.
   1157      */
   1158     private void runAppearingTransition(final ViewGroup parent, final View child) {
   1159         Animator currentAnimation = currentDisappearingAnimations.get(child);
   1160         if (currentAnimation != null) {
   1161             currentAnimation.cancel();
   1162         }
   1163         if (mAppearingAnim == null) {
   1164             if (hasListeners()) {
   1165                 ArrayList<TransitionListener> listeners =
   1166                         (ArrayList<TransitionListener>) mListeners.clone();
   1167                 for (TransitionListener listener : listeners) {
   1168                     listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
   1169                 }
   1170             }
   1171             return;
   1172         }
   1173         Animator anim = mAppearingAnim.clone();
   1174         anim.setTarget(child);
   1175         anim.setStartDelay(mAppearingDelay);
   1176         anim.setDuration(mAppearingDuration);
   1177         if (mAppearingInterpolator != sAppearingInterpolator) {
   1178             anim.setInterpolator(mAppearingInterpolator);
   1179         }
   1180         if (anim instanceof ObjectAnimator) {
   1181             ((ObjectAnimator) anim).setCurrentPlayTime(0);
   1182         }
   1183         anim.addListener(new AnimatorListenerAdapter() {
   1184             @Override
   1185             public void onAnimationEnd(Animator anim) {
   1186                 currentAppearingAnimations.remove(child);
   1187                 if (hasListeners()) {
   1188                     ArrayList<TransitionListener> listeners =
   1189                             (ArrayList<TransitionListener>) mListeners.clone();
   1190                     for (TransitionListener listener : listeners) {
   1191                         listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
   1192                     }
   1193                 }
   1194             }
   1195         });
   1196         currentAppearingAnimations.put(child, anim);
   1197         anim.start();
   1198     }
   1199 
   1200     /**
   1201      * This method runs the animation that makes a removed item disappear.
   1202      *
   1203      * @param parent The ViewGroup from which the View is being removed.
   1204      * @param child The View being removed from the ViewGroup.
   1205      */
   1206     private void runDisappearingTransition(final ViewGroup parent, final View child) {
   1207         Animator currentAnimation = currentAppearingAnimations.get(child);
   1208         if (currentAnimation != null) {
   1209             currentAnimation.cancel();
   1210         }
   1211         if (mDisappearingAnim == null) {
   1212             if (hasListeners()) {
   1213                 ArrayList<TransitionListener> listeners =
   1214                         (ArrayList<TransitionListener>) mListeners.clone();
   1215                 for (TransitionListener listener : listeners) {
   1216                     listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
   1217                 }
   1218             }
   1219             return;
   1220         }
   1221         Animator anim = mDisappearingAnim.clone();
   1222         anim.setStartDelay(mDisappearingDelay);
   1223         anim.setDuration(mDisappearingDuration);
   1224         if (mDisappearingInterpolator != sDisappearingInterpolator) {
   1225             anim.setInterpolator(mDisappearingInterpolator);
   1226         }
   1227         anim.setTarget(child);
   1228         final float preAnimAlpha = child.getAlpha();
   1229         anim.addListener(new AnimatorListenerAdapter() {
   1230             @Override
   1231             public void onAnimationEnd(Animator anim) {
   1232                 currentDisappearingAnimations.remove(child);
   1233                 child.setAlpha(preAnimAlpha);
   1234                 if (hasListeners()) {
   1235                     ArrayList<TransitionListener> listeners =
   1236                             (ArrayList<TransitionListener>) mListeners.clone();
   1237                     for (TransitionListener listener : listeners) {
   1238                         listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
   1239                     }
   1240                 }
   1241             }
   1242         });
   1243         if (anim instanceof ObjectAnimator) {
   1244             ((ObjectAnimator) anim).setCurrentPlayTime(0);
   1245         }
   1246         currentDisappearingAnimations.put(child, anim);
   1247         anim.start();
   1248     }
   1249 
   1250     /**
   1251      * This method is called by ViewGroup when a child view is about to be added to the
   1252      * container. This callback starts the process of a transition; we grab the starting
   1253      * values, listen for changes to all of the children of the container, and start appropriate
   1254      * animations.
   1255      *
   1256      * @param parent The ViewGroup to which the View is being added.
   1257      * @param child The View being added to the ViewGroup.
   1258      * @param changesLayout Whether the removal will cause changes in the layout of other views
   1259      * in the container. INVISIBLE views becoming VISIBLE will not cause changes and thus will not
   1260      * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
   1261      */
   1262     private void addChild(ViewGroup parent, View child, boolean changesLayout) {
   1263         if (parent.getWindowVisibility() != View.VISIBLE) {
   1264             return;
   1265         }
   1266         if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
   1267             // Want disappearing animations to finish up before proceeding
   1268             cancel(DISAPPEARING);
   1269         }
   1270         if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
   1271             // Also, cancel changing animations so that we start fresh ones from current locations
   1272             cancel(CHANGE_APPEARING);
   1273             cancel(CHANGING);
   1274         }
   1275         if (hasListeners() && (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
   1276             ArrayList<TransitionListener> listeners =
   1277                     (ArrayList<TransitionListener>) mListeners.clone();
   1278             for (TransitionListener listener : listeners) {
   1279                 listener.startTransition(this, parent, child, APPEARING);
   1280             }
   1281         }
   1282         if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
   1283             runChangeTransition(parent, child, APPEARING);
   1284         }
   1285         if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
   1286             runAppearingTransition(parent, child);
   1287         }
   1288     }
   1289 
   1290     private boolean hasListeners() {
   1291         return mListeners != null && mListeners.size() > 0;
   1292     }
   1293 
   1294     /**
   1295      * This method is called by ViewGroup when there is a call to layout() on the container
   1296      * with this LayoutTransition. If the CHANGING transition is enabled and if there is no other
   1297      * transition currently running on the container, then this call runs a CHANGING transition.
   1298      * The transition does not start immediately; it just sets up the mechanism to run if any
   1299      * of the children of the container change their layout parameters (similar to
   1300      * the CHANGE_APPEARING and CHANGE_DISAPPEARING transitions).
   1301      *
   1302      * @param parent The ViewGroup whose layout() method has been called.
   1303      *
   1304      * @hide
   1305      */
   1306     public void layoutChange(ViewGroup parent) {
   1307         if (parent.getWindowVisibility() != View.VISIBLE) {
   1308             return;
   1309         }
   1310         if ((mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING  && !isRunning()) {
   1311             // This method is called for all calls to layout() in the container, including
   1312             // those caused by add/remove/hide/show events, which will already have set up
   1313             // transition animations. Avoid setting up CHANGING animations in this case; only
   1314             // do so when there is not a transition already running on the container.
   1315             runChangeTransition(parent, null, CHANGING);
   1316         }
   1317     }
   1318 
   1319     /**
   1320      * This method is called by ViewGroup when a child view is about to be added to the
   1321      * container. This callback starts the process of a transition; we grab the starting
   1322      * values, listen for changes to all of the children of the container, and start appropriate
   1323      * animations.
   1324      *
   1325      * @param parent The ViewGroup to which the View is being added.
   1326      * @param child The View being added to the ViewGroup.
   1327      */
   1328     public void addChild(ViewGroup parent, View child) {
   1329         addChild(parent, child, true);
   1330     }
   1331 
   1332     /**
   1333      * @deprecated Use {@link #showChild(android.view.ViewGroup, android.view.View, int)}.
   1334      */
   1335     @Deprecated
   1336     public void showChild(ViewGroup parent, View child) {
   1337         addChild(parent, child, true);
   1338     }
   1339 
   1340     /**
   1341      * This method is called by ViewGroup when a child view is about to be made visible in the
   1342      * container. This callback starts the process of a transition; we grab the starting
   1343      * values, listen for changes to all of the children of the container, and start appropriate
   1344      * animations.
   1345      *
   1346      * @param parent The ViewGroup in which the View is being made visible.
   1347      * @param child The View being made visible.
   1348      * @param oldVisibility The previous visibility value of the child View, either
   1349      * {@link View#GONE} or {@link View#INVISIBLE}.
   1350      */
   1351     public void showChild(ViewGroup parent, View child, int oldVisibility) {
   1352         addChild(parent, child, oldVisibility == View.GONE);
   1353     }
   1354 
   1355     /**
   1356      * This method is called by ViewGroup when a child view is about to be removed from the
   1357      * container. This callback starts the process of a transition; we grab the starting
   1358      * values, listen for changes to all of the children of the container, and start appropriate
   1359      * animations.
   1360      *
   1361      * @param parent The ViewGroup from which the View is being removed.
   1362      * @param child The View being removed from the ViewGroup.
   1363      * @param changesLayout Whether the removal will cause changes in the layout of other views
   1364      * in the container. Views becoming INVISIBLE will not cause changes and thus will not
   1365      * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
   1366      */
   1367     private void removeChild(ViewGroup parent, View child, boolean changesLayout) {
   1368         if (parent.getWindowVisibility() != View.VISIBLE) {
   1369             return;
   1370         }
   1371         if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
   1372             // Want appearing animations to finish up before proceeding
   1373             cancel(APPEARING);
   1374         }
   1375         if (changesLayout &&
   1376                 (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
   1377             // Also, cancel changing animations so that we start fresh ones from current locations
   1378             cancel(CHANGE_DISAPPEARING);
   1379             cancel(CHANGING);
   1380         }
   1381         if (hasListeners() && (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
   1382             ArrayList<TransitionListener> listeners = (ArrayList<TransitionListener>) mListeners
   1383                     .clone();
   1384             for (TransitionListener listener : listeners) {
   1385                 listener.startTransition(this, parent, child, DISAPPEARING);
   1386             }
   1387         }
   1388         if (changesLayout &&
   1389                 (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
   1390             runChangeTransition(parent, child, DISAPPEARING);
   1391         }
   1392         if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
   1393             runDisappearingTransition(parent, child);
   1394         }
   1395     }
   1396 
   1397     /**
   1398      * This method is called by ViewGroup when a child view is about to be removed from the
   1399      * container. This callback starts the process of a transition; we grab the starting
   1400      * values, listen for changes to all of the children of the container, and start appropriate
   1401      * animations.
   1402      *
   1403      * @param parent The ViewGroup from which the View is being removed.
   1404      * @param child The View being removed from the ViewGroup.
   1405      */
   1406     public void removeChild(ViewGroup parent, View child) {
   1407         removeChild(parent, child, true);
   1408     }
   1409 
   1410     /**
   1411      * @deprecated Use {@link #hideChild(android.view.ViewGroup, android.view.View, int)}.
   1412      */
   1413     @Deprecated
   1414     public void hideChild(ViewGroup parent, View child) {
   1415         removeChild(parent, child, true);
   1416     }
   1417 
   1418     /**
   1419      * This method is called by ViewGroup when a child view is about to be hidden in
   1420      * container. This callback starts the process of a transition; we grab the starting
   1421      * values, listen for changes to all of the children of the container, and start appropriate
   1422      * animations.
   1423      *
   1424      * @param parent The parent ViewGroup of the View being hidden.
   1425      * @param child The View being hidden.
   1426      * @param newVisibility The new visibility value of the child View, either
   1427      * {@link View#GONE} or {@link View#INVISIBLE}.
   1428      */
   1429     public void hideChild(ViewGroup parent, View child, int newVisibility) {
   1430         removeChild(parent, child, newVisibility == View.GONE);
   1431     }
   1432 
   1433     /**
   1434      * Add a listener that will be called when the bounds of the view change due to
   1435      * layout processing.
   1436      *
   1437      * @param listener The listener that will be called when layout bounds change.
   1438      */
   1439     public void addTransitionListener(TransitionListener listener) {
   1440         if (mListeners == null) {
   1441             mListeners = new ArrayList<TransitionListener>();
   1442         }
   1443         mListeners.add(listener);
   1444     }
   1445 
   1446     /**
   1447      * Remove a listener for layout changes.
   1448      *
   1449      * @param listener The listener for layout bounds change.
   1450      */
   1451     public void removeTransitionListener(TransitionListener listener) {
   1452         if (mListeners == null) {
   1453             return;
   1454         }
   1455         mListeners.remove(listener);
   1456     }
   1457 
   1458     /**
   1459      * Gets the current list of listeners for layout changes.
   1460      * @return
   1461      */
   1462     public List<TransitionListener> getTransitionListeners() {
   1463         return mListeners;
   1464     }
   1465 
   1466     /**
   1467      * This interface is used for listening to starting and ending events for transitions.
   1468      */
   1469     public interface TransitionListener {
   1470 
   1471         /**
   1472          * This event is sent to listeners when any type of transition animation begins.
   1473          *
   1474          * @param transition The LayoutTransition sending out the event.
   1475          * @param container The ViewGroup on which the transition is playing.
   1476          * @param view The View object being affected by the transition animation.
   1477          * @param transitionType The type of transition that is beginning,
   1478          * {@link android.animation.LayoutTransition#APPEARING},
   1479          * {@link android.animation.LayoutTransition#DISAPPEARING},
   1480          * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
   1481          * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
   1482          */
   1483         public void startTransition(LayoutTransition transition, ViewGroup container,
   1484                 View view, int transitionType);
   1485 
   1486         /**
   1487          * This event is sent to listeners when any type of transition animation ends.
   1488          *
   1489          * @param transition The LayoutTransition sending out the event.
   1490          * @param container The ViewGroup on which the transition is playing.
   1491          * @param view The View object being affected by the transition animation.
   1492          * @param transitionType The type of transition that is ending,
   1493          * {@link android.animation.LayoutTransition#APPEARING},
   1494          * {@link android.animation.LayoutTransition#DISAPPEARING},
   1495          * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
   1496          * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
   1497          */
   1498         public void endTransition(LayoutTransition transition, ViewGroup container,
   1499                 View view, int transitionType);
   1500     }
   1501 
   1502 }
   1503