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      * These variables hold the animations that are currently used to run the transition effects.
    123      * These animations are set to defaults, but can be changed to custom animations by
    124      * calls to setAnimator().
    125      */
    126     private Animator mDisappearingAnim = null;
    127     private Animator mAppearingAnim = null;
    128     private Animator mChangingAppearingAnim = null;
    129     private Animator mChangingDisappearingAnim = null;
    130 
    131     /**
    132      * These are the default animations, defined in the constructor, that will be used
    133      * unless the user specifies custom animations.
    134      */
    135     private static ObjectAnimator defaultChangeIn;
    136     private static ObjectAnimator defaultChangeOut;
    137     private static ObjectAnimator defaultFadeIn;
    138     private static ObjectAnimator defaultFadeOut;
    139 
    140     /**
    141      * The default duration used by all animations.
    142      */
    143     private static long DEFAULT_DURATION = 300;
    144 
    145     /**
    146      * The durations of the four different animations
    147      */
    148     private long mChangingAppearingDuration = DEFAULT_DURATION;
    149     private long mChangingDisappearingDuration = DEFAULT_DURATION;
    150     private long mAppearingDuration = DEFAULT_DURATION;
    151     private long mDisappearingDuration = DEFAULT_DURATION;
    152 
    153     /**
    154      * The start delays of the four different animations. Note that the default behavior of
    155      * the appearing item is the default duration, since it should wait for the items to move
    156      * before fading it. Same for the changing animation when disappearing; it waits for the item
    157      * to fade out before moving the other items.
    158      */
    159     private long mAppearingDelay = DEFAULT_DURATION;
    160     private long mDisappearingDelay = 0;
    161     private long mChangingAppearingDelay = 0;
    162     private long mChangingDisappearingDelay = DEFAULT_DURATION;
    163 
    164     /**
    165      * The inter-animation delays used on the two changing animations
    166      */
    167     private long mChangingAppearingStagger = 0;
    168     private long mChangingDisappearingStagger = 0;
    169 
    170     /**
    171      * The default interpolators used for the animations
    172      */
    173     private TimeInterpolator mAppearingInterpolator = new AccelerateDecelerateInterpolator();
    174     private TimeInterpolator mDisappearingInterpolator = new AccelerateDecelerateInterpolator();
    175     private TimeInterpolator mChangingAppearingInterpolator = new DecelerateInterpolator();
    176     private TimeInterpolator mChangingDisappearingInterpolator = new DecelerateInterpolator();
    177 
    178     /**
    179      * These hashmaps are used to store the animations that are currently running as part of
    180      * the transition. The reason for this is that a further layout event should cause
    181      * existing animations to stop where they are prior to starting new animations. So
    182      * we cache all of the current animations in this map for possible cancellation on
    183      * another layout event. LinkedHashMaps are used to preserve the order in which animations
    184      * are inserted, so that we process events (such as setting up start values) in the same order.
    185      */
    186     private final HashMap<View, Animator> pendingAnimations =
    187             new HashMap<View, Animator>();
    188     private final LinkedHashMap<View, Animator> currentChangingAnimations =
    189             new LinkedHashMap<View, Animator>();
    190     private final LinkedHashMap<View, Animator> currentAppearingAnimations =
    191             new LinkedHashMap<View, Animator>();
    192     private final LinkedHashMap<View, Animator> currentDisappearingAnimations =
    193             new LinkedHashMap<View, Animator>();
    194 
    195     /**
    196      * This hashmap is used to track the listeners that have been added to the children of
    197      * a container. When a layout change occurs, an animation is created for each View, so that
    198      * the pre-layout values can be cached in that animation. Then a listener is added to the
    199      * view to see whether the layout changes the bounds of that view. If so, the animation
    200      * is set with the final values and then run. If not, the animation is not started. When
    201      * the process of setting up and running all appropriate animations is done, we need to
    202      * remove these listeners and clear out the map.
    203      */
    204     private final HashMap<View, View.OnLayoutChangeListener> layoutChangeListenerMap =
    205             new HashMap<View, View.OnLayoutChangeListener>();
    206 
    207     /**
    208      * Used to track the current delay being assigned to successive animations as they are
    209      * started. This value is incremented for each new animation, then zeroed before the next
    210      * transition begins.
    211      */
    212     private long staggerDelay;
    213 
    214     /**
    215      * The set of listeners that should be notified when APPEARING/DISAPPEARING transitions
    216      * start and end.
    217      */
    218     private ArrayList<TransitionListener> mListeners;
    219 
    220     /**
    221      * Controls whether changing animations automatically animate the parent hierarchy as well.
    222      * This behavior prevents artifacts when wrap_content layouts snap to the end state as the
    223      * transition begins, causing visual glitches and clipping.
    224      * Default value is true.
    225      */
    226     private boolean mAnimateParentHierarchy = true;
    227 
    228 
    229     /**
    230      * Constructs a LayoutTransition object. By default, the object will listen to layout
    231      * events on any ViewGroup that it is set on and will run default animations for each
    232      * type of layout event.
    233      */
    234     public LayoutTransition() {
    235         if (defaultChangeIn == null) {
    236             // "left" is just a placeholder; we'll put real properties/values in when needed
    237             PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
    238             PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
    239             PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
    240             PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
    241             PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
    242             PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
    243             defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null,
    244                     pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
    245             defaultChangeIn.setDuration(DEFAULT_DURATION);
    246             defaultChangeIn.setStartDelay(mChangingAppearingDelay);
    247             defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
    248             defaultChangeOut = defaultChangeIn.clone();
    249             defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
    250             defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
    251 
    252             defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
    253             defaultFadeIn.setDuration(DEFAULT_DURATION);
    254             defaultFadeIn.setStartDelay(mAppearingDelay);
    255             defaultFadeIn.setInterpolator(mAppearingInterpolator);
    256             defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
    257             defaultFadeOut.setDuration(DEFAULT_DURATION);
    258             defaultFadeOut.setStartDelay(mDisappearingDelay);
    259             defaultFadeOut.setInterpolator(mDisappearingInterpolator);
    260         }
    261         mChangingAppearingAnim = defaultChangeIn;
    262         mChangingDisappearingAnim = defaultChangeOut;
    263         mAppearingAnim = defaultFadeIn;
    264         mDisappearingAnim = defaultFadeOut;
    265     }
    266 
    267     /**
    268      * Sets the duration to be used by all animations of this transition object. If you want to
    269      * set the duration of just one of the animations in particular, use the
    270      * {@link #setDuration(int, long)} method.
    271      *
    272      * @param duration The length of time, in milliseconds, that the transition animations
    273      * should last.
    274      */
    275     public void setDuration(long duration) {
    276         mChangingAppearingDuration = duration;
    277         mChangingDisappearingDuration = duration;
    278         mAppearingDuration = duration;
    279         mDisappearingDuration = duration;
    280     }
    281 
    282     /**
    283      * Sets the start delay on one of the animation objects used by this transition. The
    284      * <code>transitionType</code> parameter determines the animation whose start delay
    285      * is being set.
    286      *
    287      * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    288      * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start
    289      * delay is being set.
    290      * @param delay The length of time, in milliseconds, to delay before starting the animation.
    291      * @see Animator#setStartDelay(long)
    292      */
    293     public void setStartDelay(int transitionType, long delay) {
    294         switch (transitionType) {
    295             case CHANGE_APPEARING:
    296                 mChangingAppearingDelay = delay;
    297                 break;
    298             case CHANGE_DISAPPEARING:
    299                 mChangingDisappearingDelay = delay;
    300                 break;
    301             case APPEARING:
    302                 mAppearingDelay = delay;
    303                 break;
    304             case DISAPPEARING:
    305                 mDisappearingDelay = delay;
    306                 break;
    307         }
    308     }
    309 
    310     /**
    311      * Gets the start delay on one of the animation objects used by this transition. The
    312      * <code>transitionType</code> parameter determines the animation whose start delay
    313      * is returned.
    314      *
    315      * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    316      * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start
    317      * delay is returned.
    318      * @return long The start delay of the specified animation.
    319      * @see Animator#getStartDelay()
    320      */
    321     public long getStartDelay(int transitionType) {
    322         switch (transitionType) {
    323             case CHANGE_APPEARING:
    324                 return mChangingAppearingDuration;
    325             case CHANGE_DISAPPEARING:
    326                 return mChangingDisappearingDuration;
    327             case APPEARING:
    328                 return mAppearingDuration;
    329             case DISAPPEARING:
    330                 return mDisappearingDuration;
    331         }
    332         // shouldn't reach here
    333         return 0;
    334     }
    335 
    336     /**
    337      * Sets the duration on one of the animation objects used by this transition. The
    338      * <code>transitionType</code> parameter determines the animation whose duration
    339      * is being set.
    340      *
    341      * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    342      * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
    343      * duration is being set.
    344      * @param duration The length of time, in milliseconds, that the specified animation should run.
    345      * @see Animator#setDuration(long)
    346      */
    347     public void setDuration(int transitionType, long duration) {
    348         switch (transitionType) {
    349             case CHANGE_APPEARING:
    350                 mChangingAppearingDuration = duration;
    351                 break;
    352             case CHANGE_DISAPPEARING:
    353                 mChangingDisappearingDuration = duration;
    354                 break;
    355             case APPEARING:
    356                 mAppearingDuration = duration;
    357                 break;
    358             case DISAPPEARING:
    359                 mDisappearingDuration = duration;
    360                 break;
    361         }
    362     }
    363 
    364     /**
    365      * Gets the duration on one of the animation objects used by this transition. The
    366      * <code>transitionType</code> parameter determines the animation whose duration
    367      * is returned.
    368      *
    369      * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    370      * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
    371      * duration is returned.
    372      * @return long The duration of the specified animation.
    373      * @see Animator#getDuration()
    374      */
    375     public long getDuration(int transitionType) {
    376         switch (transitionType) {
    377             case CHANGE_APPEARING:
    378                 return mChangingAppearingDuration;
    379             case CHANGE_DISAPPEARING:
    380                 return mChangingDisappearingDuration;
    381             case APPEARING:
    382                 return mAppearingDuration;
    383             case DISAPPEARING:
    384                 return mDisappearingDuration;
    385         }
    386         // shouldn't reach here
    387         return 0;
    388     }
    389 
    390     /**
    391      * Sets the length of time to delay between starting each animation during one of the
    392      * CHANGE animations.
    393      *
    394      * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}.
    395      * @param duration The length of time, in milliseconds, to delay before launching the next
    396      * animation in the sequence.
    397      */
    398     public void setStagger(int transitionType, long duration) {
    399         switch (transitionType) {
    400             case CHANGE_APPEARING:
    401                 mChangingAppearingStagger = duration;
    402                 break;
    403             case CHANGE_DISAPPEARING:
    404                 mChangingDisappearingStagger = duration;
    405                 break;
    406             // noop other cases
    407         }
    408     }
    409 
    410     /**
    411      * Tets the length of time to delay between starting each animation during one of the
    412      * CHANGE animations.
    413      *
    414      * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}.
    415      * @return long The length of time, in milliseconds, to delay before launching the next
    416      * animation in the sequence.
    417      */
    418     public long getStagger(int transitionType) {
    419         switch (transitionType) {
    420             case CHANGE_APPEARING:
    421                 return mChangingAppearingStagger;
    422             case CHANGE_DISAPPEARING:
    423                 return mChangingDisappearingStagger;
    424         }
    425         // shouldn't reach here
    426         return 0;
    427     }
    428 
    429     /**
    430      * Sets the interpolator on one of the animation objects used by this transition. The
    431      * <code>transitionType</code> parameter determines the animation whose interpolator
    432      * is being set.
    433      *
    434      * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    435      * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
    436      * duration is being set.
    437      * @param interpolator The interpolator that the specified animation should use.
    438      * @see Animator#setInterpolator(TimeInterpolator)
    439      */
    440     public void setInterpolator(int transitionType, TimeInterpolator interpolator) {
    441         switch (transitionType) {
    442             case CHANGE_APPEARING:
    443                 mChangingAppearingInterpolator = interpolator;
    444                 break;
    445             case CHANGE_DISAPPEARING:
    446                 mChangingDisappearingInterpolator = interpolator;
    447                 break;
    448             case APPEARING:
    449                 mAppearingInterpolator = interpolator;
    450                 break;
    451             case DISAPPEARING:
    452                 mDisappearingInterpolator = interpolator;
    453                 break;
    454         }
    455     }
    456 
    457     /**
    458      * Gets the interpolator on one of the animation objects used by this transition. The
    459      * <code>transitionType</code> parameter determines the animation whose interpolator
    460      * is returned.
    461      *
    462      * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    463      * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
    464      * duration is being set.
    465      * @return TimeInterpolator The interpolator that the specified animation uses.
    466      * @see Animator#setInterpolator(TimeInterpolator)
    467      */
    468     public TimeInterpolator getInterpolator(int transitionType) {
    469         switch (transitionType) {
    470             case CHANGE_APPEARING:
    471                 return mChangingAppearingInterpolator;
    472             case CHANGE_DISAPPEARING:
    473                 return mChangingDisappearingInterpolator;
    474             case APPEARING:
    475                 return mAppearingInterpolator;
    476             case DISAPPEARING:
    477                 return mDisappearingInterpolator;
    478         }
    479         // shouldn't reach here
    480         return null;
    481     }
    482 
    483     /**
    484      * Sets the animation used during one of the transition types that may run. Any
    485      * Animator object can be used, but to be most useful in the context of layout
    486      * transitions, the animation should either be a ObjectAnimator or a AnimatorSet
    487      * of animations including PropertyAnimators. Also, these ObjectAnimator objects
    488      * should be able to get and set values on their target objects automatically. For
    489      * example, a ObjectAnimator that animates the property "left" is able to set and get the
    490      * <code>left</code> property from the View objects being animated by the layout
    491      * transition. The transition works by setting target objects and properties
    492      * dynamically, according to the pre- and post-layoout values of those objects, so
    493      * having animations that can handle those properties appropriately will work best
    494      * for custom animation. The dynamic setting of values is only the case for the
    495      * CHANGE animations; the APPEARING and DISAPPEARING animations are simply run with
    496      * the values they have.
    497      *
    498      * <p>It is also worth noting that any and all animations (and their underlying
    499      * PropertyValuesHolder objects) will have their start and end values set according
    500      * to the pre- and post-layout values. So, for example, a custom animation on "alpha"
    501      * as the CHANGE_APPEARING animation will inherit the real value of alpha on the target
    502      * object (presumably 1) as its starting and ending value when the animation begins.
    503      * Animations which need to use values at the beginning and end that may not match the
    504      * values queried when the transition begins may need to use a different mechanism
    505      * than a standard ObjectAnimator object.</p>
    506      *
    507      * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    508      * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
    509      * duration is being set.
    510      * @param animator The animation being assigned. A value of <code>null</code> means that no
    511      * animation will be run for the specified transitionType.
    512      */
    513     public void setAnimator(int transitionType, Animator animator) {
    514         switch (transitionType) {
    515             case CHANGE_APPEARING:
    516                 mChangingAppearingAnim = animator;
    517                 break;
    518             case CHANGE_DISAPPEARING:
    519                 mChangingDisappearingAnim = animator;
    520                 break;
    521             case APPEARING:
    522                 mAppearingAnim = animator;
    523                 break;
    524             case DISAPPEARING:
    525                 mDisappearingAnim = animator;
    526                 break;
    527         }
    528     }
    529 
    530     /**
    531      * Gets the animation used during one of the transition types that may run.
    532      *
    533      * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
    534      * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
    535      * duration is being set.
    536      * @return Animator The animation being used for the given transition type.
    537      * @see #setAnimator(int, Animator)
    538      */
    539     public Animator getAnimator(int transitionType) {
    540         switch (transitionType) {
    541             case CHANGE_APPEARING:
    542                 return mChangingAppearingAnim;
    543             case CHANGE_DISAPPEARING:
    544                 return mChangingDisappearingAnim;
    545             case APPEARING:
    546                 return mAppearingAnim;
    547             case DISAPPEARING:
    548                 return mDisappearingAnim;
    549         }
    550         // shouldn't reach here
    551         return null;
    552     }
    553 
    554     /**
    555      * This function sets up animations on all of the views that change during layout.
    556      * For every child in the parent, we create a change animation of the appropriate
    557      * type (appearing or disappearing) and ask it to populate its start values from its
    558      * target view. We add layout listeners to all child views and listen for changes. For
    559      * those views that change, we populate the end values for those animations and start them.
    560      * Animations are not run on unchanging views.
    561      *
    562      * @param parent The container which is undergoing an appearing or disappearing change.
    563      * @param newView The view being added to or removed from the parent.
    564      * @param changeReason A value of APPEARING or DISAPPEARING, indicating whether the
    565      * transition is occuring because an item is being added to or removed from the parent.
    566      */
    567     private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) {
    568 
    569         Animator baseAnimator = (changeReason == APPEARING) ?
    570                 mChangingAppearingAnim : mChangingDisappearingAnim;
    571         // If the animation is null, there's nothing to do
    572         if (baseAnimator == null) {
    573             return;
    574         }
    575 
    576         // reset the inter-animation delay, in case we use it later
    577         staggerDelay = 0;
    578         final long duration = (changeReason == APPEARING) ?
    579                 mChangingAppearingDuration : mChangingDisappearingDuration;
    580 
    581         final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup
    582         if (!observer.isAlive()) {
    583             // If the observer's not in a good state, skip the transition
    584             return;
    585         }
    586         int numChildren = parent.getChildCount();
    587 
    588         for (int i = 0; i < numChildren; ++i) {
    589             final View child = parent.getChildAt(i);
    590 
    591             // only animate the views not being added or removed
    592             if (child != newView) {
    593                 setupChangeAnimation(parent, changeReason, baseAnimator, duration, child);
    594             }
    595         }
    596         if (mAnimateParentHierarchy) {
    597             Animator parentAnimator = (changeReason == APPEARING) ?
    598                     defaultChangeIn : defaultChangeOut;
    599             ViewGroup tempParent = parent;
    600             while (tempParent != null) {
    601                 ViewParent parentParent = tempParent.getParent();
    602                 if (parentParent instanceof ViewGroup) {
    603                     setupChangeAnimation((ViewGroup)parentParent, changeReason, parentAnimator,
    604                             duration, tempParent);
    605                     tempParent = (ViewGroup) parentParent;
    606                 } else {
    607                     tempParent = null;
    608                 }
    609 
    610             }
    611         }
    612 
    613         // This is the cleanup step. When we get this rendering event, we know that all of
    614         // the appropriate animations have been set up and run. Now we can clear out the
    615         // layout listeners.
    616         observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    617             public boolean onPreDraw() {
    618                 parent.getViewTreeObserver().removeOnPreDrawListener(this);
    619                 int count = layoutChangeListenerMap.size();
    620                 if (count > 0) {
    621                     Collection<View> views = layoutChangeListenerMap.keySet();
    622                     for (View view : views) {
    623                         View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view);
    624                         view.removeOnLayoutChangeListener(listener);
    625                     }
    626                 }
    627                 layoutChangeListenerMap.clear();
    628                 return true;
    629             }
    630         });
    631     }
    632 
    633     /**
    634      * This flag controls whether CHANGE_APPEARING or CHANGE_DISAPPEARING animations will
    635      * cause the default changing animation to be run on the parent hierarchy as well. This allows
    636      * containers of transitioning views to also transition, which may be necessary in situations
    637      * where the containers bounds change between the before/after states and may clip their
    638      * children during the transition animations. For example, layouts with wrap_content will
    639      * adjust their bounds according to the dimensions of their children.
    640      *
    641      * <p>The default changing transitions animate the bounds and scroll positions of the
    642      * target views. These are the animations that will run on the parent hierarchy, not
    643      * the custom animations that happen to be set on the transition. This allows custom
    644      * behavior for the children of the transitioning container, but uses standard behavior
    645      * of resizing/rescrolling on any changing parents.
    646      *
    647      * @param animateParentHierarchy A boolean value indicating whether the parents of
    648      * transitioning views should also be animated during the transition. Default value is true.
    649      */
    650     public void setAnimateParentHierarchy(boolean animateParentHierarchy) {
    651         mAnimateParentHierarchy = animateParentHierarchy;
    652     }
    653 
    654     /**
    655      * Utility function called by runChangingTransition for both the children and the parent
    656      * hierarchy.
    657      */
    658     private void setupChangeAnimation(final ViewGroup parent, final int changeReason,
    659             Animator baseAnimator, final long duration, final View child) {
    660         // Make a copy of the appropriate animation
    661         final Animator anim = baseAnimator.clone();
    662 
    663         // Set the target object for the animation
    664         anim.setTarget(child);
    665 
    666         // A ObjectAnimator (or AnimatorSet of them) can extract start values from
    667         // its target object
    668         anim.setupStartValues();
    669 
    670         // If there's an animation running on this view already, cancel it
    671         Animator currentAnimation = pendingAnimations.get(child);
    672         if (currentAnimation != null) {
    673             currentAnimation.cancel();
    674             pendingAnimations.remove(child);
    675         }
    676         // Cache the animation in case we need to cancel it later
    677         pendingAnimations.put(child, anim);
    678 
    679         // For the animations which don't get started, we have to have a means of
    680         // removing them from the cache, lest we leak them and their target objects.
    681         // We run an animator for the default duration+100 (an arbitrary time, but one
    682         // which should far surpass the delay between setting them up here and
    683         // handling layout events which start them.
    684         ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
    685                 setDuration(duration + 100);
    686         pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
    687             @Override
    688             public void onAnimationEnd(Animator animation) {
    689                 pendingAnimations.remove(child);
    690             }
    691         });
    692         pendingAnimRemover.start();
    693 
    694         // Add a listener to track layout changes on this view. If we don't get a callback,
    695         // then there's nothing to animate.
    696         final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
    697             public void onLayoutChange(View v, int left, int top, int right, int bottom,
    698                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
    699 
    700                 // Tell the animation to extract end values from the changed object
    701                 anim.setupEndValues();
    702                 if (anim instanceof ValueAnimator) {
    703                     boolean valuesDiffer = false;
    704                     ValueAnimator valueAnim = (ValueAnimator)anim;
    705                     PropertyValuesHolder[] oldValues = valueAnim.getValues();
    706                     for (int i = 0; i < oldValues.length; ++i) {
    707                         PropertyValuesHolder pvh = oldValues[i];
    708                         KeyframeSet keyframeSet = pvh.mKeyframeSet;
    709                         if (keyframeSet.mFirstKeyframe == null ||
    710                                 keyframeSet.mLastKeyframe == null ||
    711                                 !keyframeSet.mFirstKeyframe.getValue().equals(
    712                                 keyframeSet.mLastKeyframe.getValue())) {
    713                             valuesDiffer = true;
    714                         }
    715                     }
    716                     if (!valuesDiffer) {
    717                         return;
    718                     }
    719                 }
    720 
    721                 long startDelay;
    722                 if (changeReason == APPEARING) {
    723                     startDelay = mChangingAppearingDelay + staggerDelay;
    724                     staggerDelay += mChangingAppearingStagger;
    725                 } else {
    726                     startDelay = mChangingDisappearingDelay + staggerDelay;
    727                     staggerDelay += mChangingDisappearingStagger;
    728                 }
    729                 anim.setStartDelay(startDelay);
    730                 anim.setDuration(duration);
    731 
    732                 Animator prevAnimation = currentChangingAnimations.get(child);
    733                 if (prevAnimation != null) {
    734                     prevAnimation.cancel();
    735                 }
    736                 Animator pendingAnimation = pendingAnimations.get(child);
    737                 if (pendingAnimation != null) {
    738                     pendingAnimations.remove(child);
    739                 }
    740                 // Cache the animation in case we need to cancel it later
    741                 currentChangingAnimations.put(child, anim);
    742 
    743                 parent.requestTransitionStart(LayoutTransition.this);
    744 
    745                 // this only removes listeners whose views changed - must clear the
    746                 // other listeners later
    747                 child.removeOnLayoutChangeListener(this);
    748                 layoutChangeListenerMap.remove(child);
    749             }
    750         };
    751         // Remove the animation from the cache when it ends
    752         anim.addListener(new AnimatorListenerAdapter() {
    753 
    754             @Override
    755             public void onAnimationStart(Animator animator) {
    756                 if (mListeners != null) {
    757                     for (TransitionListener listener : mListeners) {
    758                         listener.startTransition(LayoutTransition.this, parent, child,
    759                                 changeReason == APPEARING ?
    760                                         CHANGE_APPEARING : CHANGE_DISAPPEARING);
    761                     }
    762                 }
    763             }
    764 
    765             @Override
    766             public void onAnimationCancel(Animator animator) {
    767                 child.removeOnLayoutChangeListener(listener);
    768                 layoutChangeListenerMap.remove(child);
    769             }
    770 
    771             @Override
    772             public void onAnimationEnd(Animator animator) {
    773                 currentChangingAnimations.remove(child);
    774                 if (mListeners != null) {
    775                     for (TransitionListener listener : mListeners) {
    776                         listener.endTransition(LayoutTransition.this, parent, child,
    777                                 changeReason == APPEARING ?
    778                                         CHANGE_APPEARING : CHANGE_DISAPPEARING);
    779                     }
    780                 }
    781             }
    782         });
    783 
    784         child.addOnLayoutChangeListener(listener);
    785         // cache the listener for later removal
    786         layoutChangeListenerMap.put(child, listener);
    787     }
    788 
    789     /**
    790      * Starts the animations set up for a CHANGING transition. We separate the setup of these
    791      * animations from actually starting them, to avoid side-effects that starting the animations
    792      * may have on the properties of the affected objects. After setup, we tell the affected parent
    793      * that this transition should be started. The parent informs its ViewAncestor, which then
    794      * starts the transition after the current layout/measurement phase, just prior to drawing
    795      * the view hierarchy.
    796      *
    797      * @hide
    798      */
    799     public void startChangingAnimations() {
    800         LinkedHashMap<View, Animator> currentAnimCopy =
    801                 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
    802         for (Animator anim : currentAnimCopy.values()) {
    803             if (anim instanceof ObjectAnimator) {
    804                 ((ObjectAnimator) anim).setCurrentPlayTime(0);
    805             }
    806             anim.start();
    807         }
    808     }
    809 
    810     /**
    811      * Ends the animations that are set up for a CHANGING transition. This is a variant of
    812      * startChangingAnimations() which is called when the window the transition is playing in
    813      * is not visible. We need to make sure the animations put their targets in their end states
    814      * and that the transition finishes to remove any mid-process state (such as isRunning()).
    815      *
    816      * @hide
    817      */
    818     public void endChangingAnimations() {
    819         LinkedHashMap<View, Animator> currentAnimCopy =
    820                 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
    821         for (Animator anim : currentAnimCopy.values()) {
    822             anim.start();
    823             anim.end();
    824         }
    825     }
    826 
    827     /**
    828      * Returns true if animations are running which animate layout-related properties. This
    829      * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
    830      * are running, since these animations operate on layout-related properties.
    831      *
    832      * @return true if CHANGE_APPEARING or CHANGE_DISAPPEARING animations are currently
    833      * running.
    834      */
    835     public boolean isChangingLayout() {
    836         return (currentChangingAnimations.size() > 0);
    837     }
    838 
    839     /**
    840      * Returns true if any of the animations in this transition are currently running.
    841      *
    842      * @return true if any animations in the transition are running.
    843      */
    844     public boolean isRunning() {
    845         return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 ||
    846                 currentDisappearingAnimations.size() > 0);
    847     }
    848 
    849     /**
    850      * Cancels the currently running transition. Note that we cancel() the changing animations
    851      * but end() the visibility animations. This is because this method is currently called
    852      * in the context of starting a new transition, so we want to move things from their mid-
    853      * transition positions, but we want them to have their end-transition visibility.
    854      *
    855      * @hide
    856      */
    857     public void cancel() {
    858         if (currentChangingAnimations.size() > 0) {
    859             LinkedHashMap<View, Animator> currentAnimCopy =
    860                     (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
    861             for (Animator anim : currentAnimCopy.values()) {
    862                 anim.cancel();
    863             }
    864             currentChangingAnimations.clear();
    865         }
    866         if (currentAppearingAnimations.size() > 0) {
    867             LinkedHashMap<View, Animator> currentAnimCopy =
    868                     (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
    869             for (Animator anim : currentAnimCopy.values()) {
    870                 anim.end();
    871             }
    872             currentAppearingAnimations.clear();
    873         }
    874         if (currentDisappearingAnimations.size() > 0) {
    875             LinkedHashMap<View, Animator> currentAnimCopy =
    876                     (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
    877             for (Animator anim : currentAnimCopy.values()) {
    878                 anim.end();
    879             }
    880             currentDisappearingAnimations.clear();
    881         }
    882     }
    883 
    884     /**
    885      * Cancels the specified type of transition. Note that we cancel() the changing animations
    886      * but end() the visibility animations. This is because this method is currently called
    887      * in the context of starting a new transition, so we want to move things from their mid-
    888      * transition positions, but we want them to have their end-transition visibility.
    889      *
    890      * @hide
    891      */
    892     public void cancel(int transitionType) {
    893         switch (transitionType) {
    894             case CHANGE_APPEARING:
    895             case CHANGE_DISAPPEARING:
    896                 if (currentChangingAnimations.size() > 0) {
    897                     LinkedHashMap<View, Animator> currentAnimCopy =
    898                             (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
    899                     for (Animator anim : currentAnimCopy.values()) {
    900                         anim.cancel();
    901                     }
    902                     currentChangingAnimations.clear();
    903                 }
    904                 break;
    905             case APPEARING:
    906                 if (currentAppearingAnimations.size() > 0) {
    907                     LinkedHashMap<View, Animator> currentAnimCopy =
    908                             (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
    909                     for (Animator anim : currentAnimCopy.values()) {
    910                         anim.end();
    911                     }
    912                     currentAppearingAnimations.clear();
    913                 }
    914                 break;
    915             case DISAPPEARING:
    916                 if (currentDisappearingAnimations.size() > 0) {
    917                     LinkedHashMap<View, Animator> currentAnimCopy =
    918                             (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
    919                     for (Animator anim : currentAnimCopy.values()) {
    920                         anim.end();
    921                     }
    922                     currentDisappearingAnimations.clear();
    923                 }
    924                 break;
    925         }
    926     }
    927 
    928     /**
    929      * This method runs the animation that makes an added item appear.
    930      *
    931      * @param parent The ViewGroup to which the View is being added.
    932      * @param child The View being added to the ViewGroup.
    933      */
    934     private void runAppearingTransition(final ViewGroup parent, final View child) {
    935         Animator currentAnimation = currentDisappearingAnimations.get(child);
    936         if (currentAnimation != null) {
    937             currentAnimation.cancel();
    938         }
    939         if (mAppearingAnim == null) {
    940             if (mListeners != null) {
    941                 for (TransitionListener listener : mListeners) {
    942                     listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
    943                 }
    944             }
    945             return;
    946         }
    947         Animator anim = mAppearingAnim.clone();
    948         anim.setTarget(child);
    949         anim.setStartDelay(mAppearingDelay);
    950         anim.setDuration(mAppearingDuration);
    951         if (anim instanceof ObjectAnimator) {
    952             ((ObjectAnimator) anim).setCurrentPlayTime(0);
    953         }
    954         if (mListeners != null) {
    955             anim.addListener(new AnimatorListenerAdapter() {
    956                 @Override
    957                 public void onAnimationEnd(Animator anim) {
    958                     currentAppearingAnimations.remove(child);
    959                     for (TransitionListener listener : mListeners) {
    960                         listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
    961                     }
    962                 }
    963             });
    964         }
    965         currentAppearingAnimations.put(child, anim);
    966         anim.start();
    967     }
    968 
    969     /**
    970      * This method runs the animation that makes a removed item disappear.
    971      *
    972      * @param parent The ViewGroup from which the View is being removed.
    973      * @param child The View being removed from the ViewGroup.
    974      */
    975     private void runDisappearingTransition(final ViewGroup parent, final View child) {
    976         Animator currentAnimation = currentAppearingAnimations.get(child);
    977         if (currentAnimation != null) {
    978             currentAnimation.cancel();
    979         }
    980         if (mDisappearingAnim == null) {
    981             if (mListeners != null) {
    982                 for (TransitionListener listener : mListeners) {
    983                     listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
    984                 }
    985             }
    986             return;
    987         }
    988         Animator anim = mDisappearingAnim.clone();
    989         anim.setStartDelay(mDisappearingDelay);
    990         anim.setDuration(mDisappearingDuration);
    991         anim.setTarget(child);
    992         if (mListeners != null) {
    993             anim.addListener(new AnimatorListenerAdapter() {
    994                 @Override
    995                 public void onAnimationEnd(Animator anim) {
    996                     currentDisappearingAnimations.remove(child);
    997                     for (TransitionListener listener : mListeners) {
    998                         listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
    999                     }
   1000                 }
   1001             });
   1002         }
   1003         if (anim instanceof ObjectAnimator) {
   1004             ((ObjectAnimator) anim).setCurrentPlayTime(0);
   1005         }
   1006         currentDisappearingAnimations.put(child, anim);
   1007         anim.start();
   1008     }
   1009 
   1010     /**
   1011      * This method is called by ViewGroup when a child view is about to be added to the
   1012      * container. This callback starts the process of a transition; we grab the starting
   1013      * values, listen for changes to all of the children of the container, and start appropriate
   1014      * animations.
   1015      *
   1016      * @param parent The ViewGroup to which the View is being added.
   1017      * @param child The View being added to the ViewGroup.
   1018      */
   1019     public void addChild(ViewGroup parent, View child) {
   1020         // Want disappearing animations to finish up before proceeding
   1021         cancel(DISAPPEARING);
   1022         // Also, cancel changing animations so that we start fresh ones from current locations
   1023         cancel(CHANGE_APPEARING);
   1024         if (mListeners != null) {
   1025             for (TransitionListener listener : mListeners) {
   1026                 listener.startTransition(this, parent, child, APPEARING);
   1027             }
   1028         }
   1029         runChangeTransition(parent, child, APPEARING);
   1030         runAppearingTransition(parent, child);
   1031     }
   1032 
   1033     /**
   1034      * This method is called by ViewGroup when a child view is about to be added to the
   1035      * container. This callback starts the process of a transition; we grab the starting
   1036      * values, listen for changes to all of the children of the container, and start appropriate
   1037      * animations.
   1038      *
   1039      * @param parent The ViewGroup to which the View is being added.
   1040      * @param child The View being added to the ViewGroup.
   1041      */
   1042     public void showChild(ViewGroup parent, View child) {
   1043         addChild(parent, child);
   1044     }
   1045 
   1046     /**
   1047      * This method is called by ViewGroup when a child view is about to be removed from the
   1048      * container. This callback starts the process of a transition; we grab the starting
   1049      * values, listen for changes to all of the children of the container, and start appropriate
   1050      * animations.
   1051      *
   1052      * @param parent The ViewGroup from which the View is being removed.
   1053      * @param child The View being removed from the ViewGroup.
   1054      */
   1055     public void removeChild(ViewGroup parent, View child) {
   1056         // Want appearing animations to finish up before proceeding
   1057         cancel(APPEARING);
   1058         // Also, cancel changing animations so that we start fresh ones from current locations
   1059         cancel(CHANGE_DISAPPEARING);
   1060         if (mListeners != null) {
   1061             for (TransitionListener listener : mListeners) {
   1062                 listener.startTransition(this, parent, child, DISAPPEARING);
   1063             }
   1064         }
   1065         runChangeTransition(parent, child, DISAPPEARING);
   1066         runDisappearingTransition(parent, child);
   1067     }
   1068 
   1069     /**
   1070      * This method is called by ViewGroup when a child view is about to be removed from the
   1071      * container. This callback starts the process of a transition; we grab the starting
   1072      * values, listen for changes to all of the children of the container, and start appropriate
   1073      * animations.
   1074      *
   1075      * @param parent The ViewGroup from which the View is being removed.
   1076      * @param child The View being removed from the ViewGroup.
   1077      */
   1078     public void hideChild(ViewGroup parent, View child) {
   1079         removeChild(parent, child);
   1080     }
   1081 
   1082     /**
   1083      * Add a listener that will be called when the bounds of the view change due to
   1084      * layout processing.
   1085      *
   1086      * @param listener The listener that will be called when layout bounds change.
   1087      */
   1088     public void addTransitionListener(TransitionListener listener) {
   1089         if (mListeners == null) {
   1090             mListeners = new ArrayList<TransitionListener>();
   1091         }
   1092         mListeners.add(listener);
   1093     }
   1094 
   1095     /**
   1096      * Remove a listener for layout changes.
   1097      *
   1098      * @param listener The listener for layout bounds change.
   1099      */
   1100     public void removeTransitionListener(TransitionListener listener) {
   1101         if (mListeners == null) {
   1102             return;
   1103         }
   1104         mListeners.remove(listener);
   1105     }
   1106 
   1107     /**
   1108      * Gets the current list of listeners for layout changes.
   1109      * @return
   1110      */
   1111     public List<TransitionListener> getTransitionListeners() {
   1112         return mListeners;
   1113     }
   1114 
   1115     /**
   1116      * This interface is used for listening to starting and ending events for transitions.
   1117      */
   1118     public interface TransitionListener {
   1119 
   1120         /**
   1121          * This event is sent to listeners when any type of transition animation begins.
   1122          *
   1123          * @param transition The LayoutTransition sending out the event.
   1124          * @param container The ViewGroup on which the transition is playing.
   1125          * @param view The View object being affected by the transition animation.
   1126          * @param transitionType The type of transition that is beginning,
   1127          * {@link android.animation.LayoutTransition#APPEARING},
   1128          * {@link android.animation.LayoutTransition#DISAPPEARING},
   1129          * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
   1130          * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
   1131          */
   1132         public void startTransition(LayoutTransition transition, ViewGroup container,
   1133                 View view, int transitionType);
   1134 
   1135         /**
   1136          * This event is sent to listeners when any type of transition animation ends.
   1137          *
   1138          * @param transition The LayoutTransition sending out the event.
   1139          * @param container The ViewGroup on which the transition is playing.
   1140          * @param view The View object being affected by the transition animation.
   1141          * @param transitionType The type of transition that is ending,
   1142          * {@link android.animation.LayoutTransition#APPEARING},
   1143          * {@link android.animation.LayoutTransition#DISAPPEARING},
   1144          * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
   1145          * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
   1146          */
   1147         public void endTransition(LayoutTransition transition, ViewGroup container,
   1148                 View view, int transitionType);
   1149     }
   1150 
   1151 }
   1152