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