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                         KeyframeSet keyframeSet = pvh.mKeyframeSet;
    903                         if (keyframeSet.mFirstKeyframe == null ||
    904                                 keyframeSet.mLastKeyframe == null ||
    905                                 !keyframeSet.mFirstKeyframe.getValue().equals(
    906                                 keyframeSet.mLastKeyframe.getValue())) {
    907                             valuesDiffer = true;
    908                         }
    909                     }
    910                     if (!valuesDiffer) {
    911                         return;
    912                     }
    913                 }
    914 
    915                 long startDelay = 0;
    916                 switch (changeReason) {
    917                     case APPEARING:
    918                         startDelay = mChangingAppearingDelay + staggerDelay;
    919                         staggerDelay += mChangingAppearingStagger;
    920                         if (mChangingAppearingInterpolator != sChangingAppearingInterpolator) {
    921                             anim.setInterpolator(mChangingAppearingInterpolator);
    922                         }
    923                         break;
    924                     case DISAPPEARING:
    925                         startDelay = mChangingDisappearingDelay + staggerDelay;
    926                         staggerDelay += mChangingDisappearingStagger;
    927                         if (mChangingDisappearingInterpolator !=
    928                                 sChangingDisappearingInterpolator) {
    929                             anim.setInterpolator(mChangingDisappearingInterpolator);
    930                         }
    931                         break;
    932                     case CHANGING:
    933                         startDelay = mChangingDelay + staggerDelay;
    934                         staggerDelay += mChangingStagger;
    935                         if (mChangingInterpolator != sChangingInterpolator) {
    936                             anim.setInterpolator(mChangingInterpolator);
    937                         }
    938                         break;
    939                 }
    940                 anim.setStartDelay(startDelay);
    941                 anim.setDuration(duration);
    942 
    943                 Animator prevAnimation = currentChangingAnimations.get(child);
    944                 if (prevAnimation != null) {
    945                     prevAnimation.cancel();
    946                 }
    947                 Animator pendingAnimation = pendingAnimations.get(child);
    948                 if (pendingAnimation != null) {
    949                     pendingAnimations.remove(child);
    950                 }
    951                 // Cache the animation in case we need to cancel it later
    952                 currentChangingAnimations.put(child, anim);
    953 
    954                 parent.requestTransitionStart(LayoutTransition.this);
    955 
    956                 // this only removes listeners whose views changed - must clear the
    957                 // other listeners later
    958                 child.removeOnLayoutChangeListener(this);
    959                 layoutChangeListenerMap.remove(child);
    960             }
    961         };
    962         // Remove the animation from the cache when it ends
    963         anim.addListener(new AnimatorListenerAdapter() {
    964 
    965             @Override
    966             public void onAnimationStart(Animator animator) {
    967                 if (hasListeners()) {
    968                     ArrayList<TransitionListener> listeners =
    969                             (ArrayList<TransitionListener>) mListeners.clone();
    970                     for (TransitionListener listener : listeners) {
    971                         listener.startTransition(LayoutTransition.this, parent, child,
    972                                 changeReason == APPEARING ?
    973                                         CHANGE_APPEARING : changeReason == DISAPPEARING ?
    974                                         CHANGE_DISAPPEARING : CHANGING);
    975                     }
    976                 }
    977             }
    978 
    979             @Override
    980             public void onAnimationCancel(Animator animator) {
    981                 child.removeOnLayoutChangeListener(listener);
    982                 layoutChangeListenerMap.remove(child);
    983             }
    984 
    985             @Override
    986             public void onAnimationEnd(Animator animator) {
    987                 currentChangingAnimations.remove(child);
    988                 if (hasListeners()) {
    989                     ArrayList<TransitionListener> listeners =
    990                             (ArrayList<TransitionListener>) mListeners.clone();
    991                     for (TransitionListener listener : listeners) {
    992                         listener.endTransition(LayoutTransition.this, parent, child,
    993                                 changeReason == APPEARING ?
    994                                         CHANGE_APPEARING : changeReason == DISAPPEARING ?
    995                                         CHANGE_DISAPPEARING : CHANGING);
    996                     }
    997                 }
    998             }
    999         });
   1000 
   1001         child.addOnLayoutChangeListener(listener);
   1002         // cache the listener for later removal
   1003         layoutChangeListenerMap.put(child, listener);
   1004     }
   1005 
   1006     /**
   1007      * Starts the animations set up for a CHANGING transition. We separate the setup of these
   1008      * animations from actually starting them, to avoid side-effects that starting the animations
   1009      * may have on the properties of the affected objects. After setup, we tell the affected parent
   1010      * that this transition should be started. The parent informs its ViewAncestor, which then
   1011      * starts the transition after the current layout/measurement phase, just prior to drawing
   1012      * the view hierarchy.
   1013      *
   1014      * @hide
   1015      */
   1016     public void startChangingAnimations() {
   1017         LinkedHashMap<View, Animator> currentAnimCopy =
   1018                 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
   1019         for (Animator anim : currentAnimCopy.values()) {
   1020             if (anim instanceof ObjectAnimator) {
   1021                 ((ObjectAnimator) anim).setCurrentPlayTime(0);
   1022             }
   1023             anim.start();
   1024         }
   1025     }
   1026 
   1027     /**
   1028      * Ends the animations that are set up for a CHANGING transition. This is a variant of
   1029      * startChangingAnimations() which is called when the window the transition is playing in
   1030      * is not visible. We need to make sure the animations put their targets in their end states
   1031      * and that the transition finishes to remove any mid-process state (such as isRunning()).
   1032      *
   1033      * @hide
   1034      */
   1035     public void endChangingAnimations() {
   1036         LinkedHashMap<View, Animator> currentAnimCopy =
   1037                 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
   1038         for (Animator anim : currentAnimCopy.values()) {
   1039             anim.start();
   1040             anim.end();
   1041         }
   1042         // listeners should clean up the currentChangingAnimations list, but just in case...
   1043         currentChangingAnimations.clear();
   1044     }
   1045 
   1046     /**
   1047      * Returns true if animations are running which animate layout-related properties. This
   1048      * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
   1049      * are running, since these animations operate on layout-related properties.
   1050      *
   1051      * @return true if CHANGE_APPEARING or CHANGE_DISAPPEARING animations are currently
   1052      * running.
   1053      */
   1054     public boolean isChangingLayout() {
   1055         return (currentChangingAnimations.size() > 0);
   1056     }
   1057 
   1058     /**
   1059      * Returns true if any of the animations in this transition are currently running.
   1060      *
   1061      * @return true if any animations in the transition are running.
   1062      */
   1063     public boolean isRunning() {
   1064         return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 ||
   1065                 currentDisappearingAnimations.size() > 0);
   1066     }
   1067 
   1068     /**
   1069      * Cancels the currently running transition. Note that we cancel() the changing animations
   1070      * but end() the visibility animations. This is because this method is currently called
   1071      * in the context of starting a new transition, so we want to move things from their mid-
   1072      * transition positions, but we want them to have their end-transition visibility.
   1073      *
   1074      * @hide
   1075      */
   1076     public void cancel() {
   1077         if (currentChangingAnimations.size() > 0) {
   1078             LinkedHashMap<View, Animator> currentAnimCopy =
   1079                     (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
   1080             for (Animator anim : currentAnimCopy.values()) {
   1081                 anim.cancel();
   1082             }
   1083             currentChangingAnimations.clear();
   1084         }
   1085         if (currentAppearingAnimations.size() > 0) {
   1086             LinkedHashMap<View, Animator> currentAnimCopy =
   1087                     (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
   1088             for (Animator anim : currentAnimCopy.values()) {
   1089                 anim.end();
   1090             }
   1091             currentAppearingAnimations.clear();
   1092         }
   1093         if (currentDisappearingAnimations.size() > 0) {
   1094             LinkedHashMap<View, Animator> currentAnimCopy =
   1095                     (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
   1096             for (Animator anim : currentAnimCopy.values()) {
   1097                 anim.end();
   1098             }
   1099             currentDisappearingAnimations.clear();
   1100         }
   1101     }
   1102 
   1103     /**
   1104      * Cancels the specified type of transition. Note that we cancel() the changing animations
   1105      * but end() the visibility animations. This is because this method is currently called
   1106      * in the context of starting a new transition, so we want to move things from their mid-
   1107      * transition positions, but we want them to have their end-transition visibility.
   1108      *
   1109      * @hide
   1110      */
   1111     public void cancel(int transitionType) {
   1112         switch (transitionType) {
   1113             case CHANGE_APPEARING:
   1114             case CHANGE_DISAPPEARING:
   1115             case CHANGING:
   1116                 if (currentChangingAnimations.size() > 0) {
   1117                     LinkedHashMap<View, Animator> currentAnimCopy =
   1118                             (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
   1119                     for (Animator anim : currentAnimCopy.values()) {
   1120                         anim.cancel();
   1121                     }
   1122                     currentChangingAnimations.clear();
   1123                 }
   1124                 break;
   1125             case APPEARING:
   1126                 if (currentAppearingAnimations.size() > 0) {
   1127                     LinkedHashMap<View, Animator> currentAnimCopy =
   1128                             (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
   1129                     for (Animator anim : currentAnimCopy.values()) {
   1130                         anim.end();
   1131                     }
   1132                     currentAppearingAnimations.clear();
   1133                 }
   1134                 break;
   1135             case DISAPPEARING:
   1136                 if (currentDisappearingAnimations.size() > 0) {
   1137                     LinkedHashMap<View, Animator> currentAnimCopy =
   1138                             (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
   1139                     for (Animator anim : currentAnimCopy.values()) {
   1140                         anim.end();
   1141                     }
   1142                     currentDisappearingAnimations.clear();
   1143                 }
   1144                 break;
   1145         }
   1146     }
   1147 
   1148     /**
   1149      * This method runs the animation that makes an added item appear.
   1150      *
   1151      * @param parent The ViewGroup to which the View is being added.
   1152      * @param child The View being added to the ViewGroup.
   1153      */
   1154     private void runAppearingTransition(final ViewGroup parent, final View child) {
   1155         Animator currentAnimation = currentDisappearingAnimations.get(child);
   1156         if (currentAnimation != null) {
   1157             currentAnimation.cancel();
   1158         }
   1159         if (mAppearingAnim == null) {
   1160             if (hasListeners()) {
   1161                 ArrayList<TransitionListener> listeners =
   1162                         (ArrayList<TransitionListener>) mListeners.clone();
   1163                 for (TransitionListener listener : listeners) {
   1164                     listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
   1165                 }
   1166             }
   1167             return;
   1168         }
   1169         Animator anim = mAppearingAnim.clone();
   1170         anim.setTarget(child);
   1171         anim.setStartDelay(mAppearingDelay);
   1172         anim.setDuration(mAppearingDuration);
   1173         if (mAppearingInterpolator != sAppearingInterpolator) {
   1174             anim.setInterpolator(mAppearingInterpolator);
   1175         }
   1176         if (anim instanceof ObjectAnimator) {
   1177             ((ObjectAnimator) anim).setCurrentPlayTime(0);
   1178         }
   1179         anim.addListener(new AnimatorListenerAdapter() {
   1180             @Override
   1181             public void onAnimationEnd(Animator anim) {
   1182                 currentAppearingAnimations.remove(child);
   1183                 if (hasListeners()) {
   1184                     ArrayList<TransitionListener> listeners =
   1185                             (ArrayList<TransitionListener>) mListeners.clone();
   1186                     for (TransitionListener listener : listeners) {
   1187                         listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
   1188                     }
   1189                 }
   1190             }
   1191         });
   1192         currentAppearingAnimations.put(child, anim);
   1193         anim.start();
   1194     }
   1195 
   1196     /**
   1197      * This method runs the animation that makes a removed item disappear.
   1198      *
   1199      * @param parent The ViewGroup from which the View is being removed.
   1200      * @param child The View being removed from the ViewGroup.
   1201      */
   1202     private void runDisappearingTransition(final ViewGroup parent, final View child) {
   1203         Animator currentAnimation = currentAppearingAnimations.get(child);
   1204         if (currentAnimation != null) {
   1205             currentAnimation.cancel();
   1206         }
   1207         if (mDisappearingAnim == null) {
   1208             if (hasListeners()) {
   1209                 ArrayList<TransitionListener> listeners =
   1210                         (ArrayList<TransitionListener>) mListeners.clone();
   1211                 for (TransitionListener listener : listeners) {
   1212                     listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
   1213                 }
   1214             }
   1215             return;
   1216         }
   1217         Animator anim = mDisappearingAnim.clone();
   1218         anim.setStartDelay(mDisappearingDelay);
   1219         anim.setDuration(mDisappearingDuration);
   1220         if (mDisappearingInterpolator != sDisappearingInterpolator) {
   1221             anim.setInterpolator(mDisappearingInterpolator);
   1222         }
   1223         anim.setTarget(child);
   1224         final float preAnimAlpha = child.getAlpha();
   1225         anim.addListener(new AnimatorListenerAdapter() {
   1226             @Override
   1227             public void onAnimationEnd(Animator anim) {
   1228                 currentDisappearingAnimations.remove(child);
   1229                 child.setAlpha(preAnimAlpha);
   1230                 if (hasListeners()) {
   1231                     ArrayList<TransitionListener> listeners =
   1232                             (ArrayList<TransitionListener>) mListeners.clone();
   1233                     for (TransitionListener listener : listeners) {
   1234                         listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
   1235                     }
   1236                 }
   1237             }
   1238         });
   1239         if (anim instanceof ObjectAnimator) {
   1240             ((ObjectAnimator) anim).setCurrentPlayTime(0);
   1241         }
   1242         currentDisappearingAnimations.put(child, anim);
   1243         anim.start();
   1244     }
   1245 
   1246     /**
   1247      * This method is called by ViewGroup when a child view is about to be added to the
   1248      * container. This callback starts the process of a transition; we grab the starting
   1249      * values, listen for changes to all of the children of the container, and start appropriate
   1250      * animations.
   1251      *
   1252      * @param parent The ViewGroup to which the View is being added.
   1253      * @param child The View being added to the ViewGroup.
   1254      * @param changesLayout Whether the removal will cause changes in the layout of other views
   1255      * in the container. INVISIBLE views becoming VISIBLE will not cause changes and thus will not
   1256      * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
   1257      */
   1258     private void addChild(ViewGroup parent, View child, boolean changesLayout) {
   1259         if (parent.getWindowVisibility() != View.VISIBLE) {
   1260             return;
   1261         }
   1262         if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
   1263             // Want disappearing animations to finish up before proceeding
   1264             cancel(DISAPPEARING);
   1265         }
   1266         if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
   1267             // Also, cancel changing animations so that we start fresh ones from current locations
   1268             cancel(CHANGE_APPEARING);
   1269             cancel(CHANGING);
   1270         }
   1271         if (hasListeners() && (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
   1272             ArrayList<TransitionListener> listeners =
   1273                     (ArrayList<TransitionListener>) mListeners.clone();
   1274             for (TransitionListener listener : listeners) {
   1275                 listener.startTransition(this, parent, child, APPEARING);
   1276             }
   1277         }
   1278         if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
   1279             runChangeTransition(parent, child, APPEARING);
   1280         }
   1281         if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
   1282             runAppearingTransition(parent, child);
   1283         }
   1284     }
   1285 
   1286     private boolean hasListeners() {
   1287         return mListeners != null && mListeners.size() > 0;
   1288     }
   1289 
   1290     /**
   1291      * This method is called by ViewGroup when there is a call to layout() on the container
   1292      * with this LayoutTransition. If the CHANGING transition is enabled and if there is no other
   1293      * transition currently running on the container, then this call runs a CHANGING transition.
   1294      * The transition does not start immediately; it just sets up the mechanism to run if any
   1295      * of the children of the container change their layout parameters (similar to
   1296      * the CHANGE_APPEARING and CHANGE_DISAPPEARING transitions).
   1297      *
   1298      * @param parent The ViewGroup whose layout() method has been called.
   1299      *
   1300      * @hide
   1301      */
   1302     public void layoutChange(ViewGroup parent) {
   1303         if (parent.getWindowVisibility() != View.VISIBLE) {
   1304             return;
   1305         }
   1306         if ((mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING  && !isRunning()) {
   1307             // This method is called for all calls to layout() in the container, including
   1308             // those caused by add/remove/hide/show events, which will already have set up
   1309             // transition animations. Avoid setting up CHANGING animations in this case; only
   1310             // do so when there is not a transition already running on the container.
   1311             runChangeTransition(parent, null, CHANGING);
   1312         }
   1313     }
   1314 
   1315     /**
   1316      * This method is called by ViewGroup when a child view is about to be added to the
   1317      * container. This callback starts the process of a transition; we grab the starting
   1318      * values, listen for changes to all of the children of the container, and start appropriate
   1319      * animations.
   1320      *
   1321      * @param parent The ViewGroup to which the View is being added.
   1322      * @param child The View being added to the ViewGroup.
   1323      */
   1324     public void addChild(ViewGroup parent, View child) {
   1325         addChild(parent, child, true);
   1326     }
   1327 
   1328     /**
   1329      * @deprecated Use {@link #showChild(android.view.ViewGroup, android.view.View, int)}.
   1330      */
   1331     @Deprecated
   1332     public void showChild(ViewGroup parent, View child) {
   1333         addChild(parent, child, true);
   1334     }
   1335 
   1336     /**
   1337      * This method is called by ViewGroup when a child view is about to be made visible in the
   1338      * container. This callback starts the process of a transition; we grab the starting
   1339      * values, listen for changes to all of the children of the container, and start appropriate
   1340      * animations.
   1341      *
   1342      * @param parent The ViewGroup in which the View is being made visible.
   1343      * @param child The View being made visible.
   1344      * @param oldVisibility The previous visibility value of the child View, either
   1345      * {@link View#GONE} or {@link View#INVISIBLE}.
   1346      */
   1347     public void showChild(ViewGroup parent, View child, int oldVisibility) {
   1348         addChild(parent, child, oldVisibility == View.GONE);
   1349     }
   1350 
   1351     /**
   1352      * This method is called by ViewGroup when a child view is about to be removed from the
   1353      * container. This callback starts the process of a transition; we grab the starting
   1354      * values, listen for changes to all of the children of the container, and start appropriate
   1355      * animations.
   1356      *
   1357      * @param parent The ViewGroup from which the View is being removed.
   1358      * @param child The View being removed from the ViewGroup.
   1359      * @param changesLayout Whether the removal will cause changes in the layout of other views
   1360      * in the container. Views becoming INVISIBLE will not cause changes and thus will not
   1361      * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
   1362      */
   1363     private void removeChild(ViewGroup parent, View child, boolean changesLayout) {
   1364         if (parent.getWindowVisibility() != View.VISIBLE) {
   1365             return;
   1366         }
   1367         if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
   1368             // Want appearing animations to finish up before proceeding
   1369             cancel(APPEARING);
   1370         }
   1371         if (changesLayout &&
   1372                 (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
   1373             // Also, cancel changing animations so that we start fresh ones from current locations
   1374             cancel(CHANGE_DISAPPEARING);
   1375             cancel(CHANGING);
   1376         }
   1377         if (hasListeners() && (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
   1378             ArrayList<TransitionListener> listeners = (ArrayList<TransitionListener>) mListeners
   1379                     .clone();
   1380             for (TransitionListener listener : listeners) {
   1381                 listener.startTransition(this, parent, child, DISAPPEARING);
   1382             }
   1383         }
   1384         if (changesLayout &&
   1385                 (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
   1386             runChangeTransition(parent, child, DISAPPEARING);
   1387         }
   1388         if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
   1389             runDisappearingTransition(parent, child);
   1390         }
   1391     }
   1392 
   1393     /**
   1394      * This method is called by ViewGroup when a child view is about to be removed from the
   1395      * container. This callback starts the process of a transition; we grab the starting
   1396      * values, listen for changes to all of the children of the container, and start appropriate
   1397      * animations.
   1398      *
   1399      * @param parent The ViewGroup from which the View is being removed.
   1400      * @param child The View being removed from the ViewGroup.
   1401      */
   1402     public void removeChild(ViewGroup parent, View child) {
   1403         removeChild(parent, child, true);
   1404     }
   1405 
   1406     /**
   1407      * @deprecated Use {@link #hideChild(android.view.ViewGroup, android.view.View, int)}.
   1408      */
   1409     @Deprecated
   1410     public void hideChild(ViewGroup parent, View child) {
   1411         removeChild(parent, child, true);
   1412     }
   1413 
   1414     /**
   1415      * This method is called by ViewGroup when a child view is about to be hidden in
   1416      * container. This callback starts the process of a transition; we grab the starting
   1417      * values, listen for changes to all of the children of the container, and start appropriate
   1418      * animations.
   1419      *
   1420      * @param parent The parent ViewGroup of the View being hidden.
   1421      * @param child The View being hidden.
   1422      * @param newVisibility The new visibility value of the child View, either
   1423      * {@link View#GONE} or {@link View#INVISIBLE}.
   1424      */
   1425     public void hideChild(ViewGroup parent, View child, int newVisibility) {
   1426         removeChild(parent, child, newVisibility == View.GONE);
   1427     }
   1428 
   1429     /**
   1430      * Add a listener that will be called when the bounds of the view change due to
   1431      * layout processing.
   1432      *
   1433      * @param listener The listener that will be called when layout bounds change.
   1434      */
   1435     public void addTransitionListener(TransitionListener listener) {
   1436         if (mListeners == null) {
   1437             mListeners = new ArrayList<TransitionListener>();
   1438         }
   1439         mListeners.add(listener);
   1440     }
   1441 
   1442     /**
   1443      * Remove a listener for layout changes.
   1444      *
   1445      * @param listener The listener for layout bounds change.
   1446      */
   1447     public void removeTransitionListener(TransitionListener listener) {
   1448         if (mListeners == null) {
   1449             return;
   1450         }
   1451         mListeners.remove(listener);
   1452     }
   1453 
   1454     /**
   1455      * Gets the current list of listeners for layout changes.
   1456      * @return
   1457      */
   1458     public List<TransitionListener> getTransitionListeners() {
   1459         return mListeners;
   1460     }
   1461 
   1462     /**
   1463      * This interface is used for listening to starting and ending events for transitions.
   1464      */
   1465     public interface TransitionListener {
   1466 
   1467         /**
   1468          * This event is sent to listeners when any type of transition animation begins.
   1469          *
   1470          * @param transition The LayoutTransition sending out the event.
   1471          * @param container The ViewGroup on which the transition is playing.
   1472          * @param view The View object being affected by the transition animation.
   1473          * @param transitionType The type of transition that is beginning,
   1474          * {@link android.animation.LayoutTransition#APPEARING},
   1475          * {@link android.animation.LayoutTransition#DISAPPEARING},
   1476          * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
   1477          * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
   1478          */
   1479         public void startTransition(LayoutTransition transition, ViewGroup container,
   1480                 View view, int transitionType);
   1481 
   1482         /**
   1483          * This event is sent to listeners when any type of transition animation ends.
   1484          *
   1485          * @param transition The LayoutTransition sending out the event.
   1486          * @param container The ViewGroup on which the transition is playing.
   1487          * @param view The View object being affected by the transition animation.
   1488          * @param transitionType The type of transition that is ending,
   1489          * {@link android.animation.LayoutTransition#APPEARING},
   1490          * {@link android.animation.LayoutTransition#DISAPPEARING},
   1491          * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
   1492          * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
   1493          */
   1494         public void endTransition(LayoutTransition transition, ViewGroup container,
   1495                 View view, int transitionType);
   1496     }
   1497 
   1498 }
   1499