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