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