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 
    661         // If we already have a listener for this child, then we've already set up the
    662         // changing animation we need. Multiple calls for a child may occur when several
    663         // add/remove operations are run at once on a container; each one will trigger
    664         // changes for the existing children in the container.
    665         if (layoutChangeListenerMap.get(child) != null) {
    666             return;
    667         }
    668 
    669         // Make a copy of the appropriate animation
    670         final Animator anim = baseAnimator.clone();
    671 
    672         // Set the target object for the animation
    673         anim.setTarget(child);
    674 
    675         // A ObjectAnimator (or AnimatorSet of them) can extract start values from
    676         // its target object
    677         anim.setupStartValues();
    678 
    679         // If there's an animation running on this view already, cancel it
    680         Animator currentAnimation = pendingAnimations.get(child);
    681         if (currentAnimation != null) {
    682             currentAnimation.cancel();
    683             pendingAnimations.remove(child);
    684         }
    685         // Cache the animation in case we need to cancel it later
    686         pendingAnimations.put(child, anim);
    687 
    688         // For the animations which don't get started, we have to have a means of
    689         // removing them from the cache, lest we leak them and their target objects.
    690         // We run an animator for the default duration+100 (an arbitrary time, but one
    691         // which should far surpass the delay between setting them up here and
    692         // handling layout events which start them.
    693         ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
    694                 setDuration(duration + 100);
    695         pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
    696             @Override
    697             public void onAnimationEnd(Animator animation) {
    698                 pendingAnimations.remove(child);
    699             }
    700         });
    701         pendingAnimRemover.start();
    702 
    703         // Add a listener to track layout changes on this view. If we don't get a callback,
    704         // then there's nothing to animate.
    705         final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
    706             public void onLayoutChange(View v, int left, int top, int right, int bottom,
    707                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
    708 
    709                 // Tell the animation to extract end values from the changed object
    710                 anim.setupEndValues();
    711                 if (anim instanceof ValueAnimator) {
    712                     boolean valuesDiffer = false;
    713                     ValueAnimator valueAnim = (ValueAnimator)anim;
    714                     PropertyValuesHolder[] oldValues = valueAnim.getValues();
    715                     for (int i = 0; i < oldValues.length; ++i) {
    716                         PropertyValuesHolder pvh = oldValues[i];
    717                         KeyframeSet keyframeSet = pvh.mKeyframeSet;
    718                         if (keyframeSet.mFirstKeyframe == null ||
    719                                 keyframeSet.mLastKeyframe == null ||
    720                                 !keyframeSet.mFirstKeyframe.getValue().equals(
    721                                 keyframeSet.mLastKeyframe.getValue())) {
    722                             valuesDiffer = true;
    723                         }
    724                     }
    725                     if (!valuesDiffer) {
    726                         return;
    727                     }
    728                 }
    729 
    730                 long startDelay;
    731                 if (changeReason == APPEARING) {
    732                     startDelay = mChangingAppearingDelay + staggerDelay;
    733                     staggerDelay += mChangingAppearingStagger;
    734                 } else {
    735                     startDelay = mChangingDisappearingDelay + staggerDelay;
    736                     staggerDelay += mChangingDisappearingStagger;
    737                 }
    738                 anim.setStartDelay(startDelay);
    739                 anim.setDuration(duration);
    740 
    741                 Animator prevAnimation = currentChangingAnimations.get(child);
    742                 if (prevAnimation != null) {
    743                     prevAnimation.cancel();
    744                 }
    745                 Animator pendingAnimation = pendingAnimations.get(child);
    746                 if (pendingAnimation != null) {
    747                     pendingAnimations.remove(child);
    748                 }
    749                 // Cache the animation in case we need to cancel it later
    750                 currentChangingAnimations.put(child, anim);
    751 
    752                 parent.requestTransitionStart(LayoutTransition.this);
    753 
    754                 // this only removes listeners whose views changed - must clear the
    755                 // other listeners later
    756                 child.removeOnLayoutChangeListener(this);
    757                 layoutChangeListenerMap.remove(child);
    758             }
    759         };
    760         // Remove the animation from the cache when it ends
    761         anim.addListener(new AnimatorListenerAdapter() {
    762 
    763             @Override
    764             public void onAnimationStart(Animator animator) {
    765                 if (mListeners != null) {
    766                     for (TransitionListener listener : mListeners) {
    767                         listener.startTransition(LayoutTransition.this, parent, child,
    768                                 changeReason == APPEARING ?
    769                                         CHANGE_APPEARING : CHANGE_DISAPPEARING);
    770                     }
    771                 }
    772             }
    773 
    774             @Override
    775             public void onAnimationCancel(Animator animator) {
    776                 child.removeOnLayoutChangeListener(listener);
    777                 layoutChangeListenerMap.remove(child);
    778             }
    779 
    780             @Override
    781             public void onAnimationEnd(Animator animator) {
    782                 currentChangingAnimations.remove(child);
    783                 if (mListeners != null) {
    784                     for (TransitionListener listener : mListeners) {
    785                         listener.endTransition(LayoutTransition.this, parent, child,
    786                                 changeReason == APPEARING ?
    787                                         CHANGE_APPEARING : CHANGE_DISAPPEARING);
    788                     }
    789                 }
    790             }
    791         });
    792 
    793         child.addOnLayoutChangeListener(listener);
    794         // cache the listener for later removal
    795         layoutChangeListenerMap.put(child, listener);
    796     }
    797 
    798     /**
    799      * Starts the animations set up for a CHANGING transition. We separate the setup of these
    800      * animations from actually starting them, to avoid side-effects that starting the animations
    801      * may have on the properties of the affected objects. After setup, we tell the affected parent
    802      * that this transition should be started. The parent informs its ViewAncestor, which then
    803      * starts the transition after the current layout/measurement phase, just prior to drawing
    804      * the view hierarchy.
    805      *
    806      * @hide
    807      */
    808     public void startChangingAnimations() {
    809         LinkedHashMap<View, Animator> currentAnimCopy =
    810                 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
    811         for (Animator anim : currentAnimCopy.values()) {
    812             if (anim instanceof ObjectAnimator) {
    813                 ((ObjectAnimator) anim).setCurrentPlayTime(0);
    814             }
    815             anim.start();
    816         }
    817     }
    818 
    819     /**
    820      * Ends the animations that are set up for a CHANGING transition. This is a variant of
    821      * startChangingAnimations() which is called when the window the transition is playing in
    822      * is not visible. We need to make sure the animations put their targets in their end states
    823      * and that the transition finishes to remove any mid-process state (such as isRunning()).
    824      *
    825      * @hide
    826      */
    827     public void endChangingAnimations() {
    828         LinkedHashMap<View, Animator> currentAnimCopy =
    829                 (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
    830         for (Animator anim : currentAnimCopy.values()) {
    831             anim.start();
    832             anim.end();
    833         }
    834     }
    835 
    836     /**
    837      * Returns true if animations are running which animate layout-related properties. This
    838      * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
    839      * are running, since these animations operate on layout-related properties.
    840      *
    841      * @return true if CHANGE_APPEARING or CHANGE_DISAPPEARING animations are currently
    842      * running.
    843      */
    844     public boolean isChangingLayout() {
    845         return (currentChangingAnimations.size() > 0);
    846     }
    847 
    848     /**
    849      * Returns true if any of the animations in this transition are currently running.
    850      *
    851      * @return true if any animations in the transition are running.
    852      */
    853     public boolean isRunning() {
    854         return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 ||
    855                 currentDisappearingAnimations.size() > 0);
    856     }
    857 
    858     /**
    859      * Cancels the currently running transition. Note that we cancel() the changing animations
    860      * but end() the visibility animations. This is because this method is currently called
    861      * in the context of starting a new transition, so we want to move things from their mid-
    862      * transition positions, but we want them to have their end-transition visibility.
    863      *
    864      * @hide
    865      */
    866     public void cancel() {
    867         if (currentChangingAnimations.size() > 0) {
    868             LinkedHashMap<View, Animator> currentAnimCopy =
    869                     (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
    870             for (Animator anim : currentAnimCopy.values()) {
    871                 anim.cancel();
    872             }
    873             currentChangingAnimations.clear();
    874         }
    875         if (currentAppearingAnimations.size() > 0) {
    876             LinkedHashMap<View, Animator> currentAnimCopy =
    877                     (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
    878             for (Animator anim : currentAnimCopy.values()) {
    879                 anim.end();
    880             }
    881             currentAppearingAnimations.clear();
    882         }
    883         if (currentDisappearingAnimations.size() > 0) {
    884             LinkedHashMap<View, Animator> currentAnimCopy =
    885                     (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
    886             for (Animator anim : currentAnimCopy.values()) {
    887                 anim.end();
    888             }
    889             currentDisappearingAnimations.clear();
    890         }
    891     }
    892 
    893     /**
    894      * Cancels the specified type of transition. Note that we cancel() the changing animations
    895      * but end() the visibility animations. This is because this method is currently called
    896      * in the context of starting a new transition, so we want to move things from their mid-
    897      * transition positions, but we want them to have their end-transition visibility.
    898      *
    899      * @hide
    900      */
    901     public void cancel(int transitionType) {
    902         switch (transitionType) {
    903             case CHANGE_APPEARING:
    904             case CHANGE_DISAPPEARING:
    905                 if (currentChangingAnimations.size() > 0) {
    906                     LinkedHashMap<View, Animator> currentAnimCopy =
    907                             (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
    908                     for (Animator anim : currentAnimCopy.values()) {
    909                         anim.cancel();
    910                     }
    911                     currentChangingAnimations.clear();
    912                 }
    913                 break;
    914             case APPEARING:
    915                 if (currentAppearingAnimations.size() > 0) {
    916                     LinkedHashMap<View, Animator> currentAnimCopy =
    917                             (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
    918                     for (Animator anim : currentAnimCopy.values()) {
    919                         anim.end();
    920                     }
    921                     currentAppearingAnimations.clear();
    922                 }
    923                 break;
    924             case DISAPPEARING:
    925                 if (currentDisappearingAnimations.size() > 0) {
    926                     LinkedHashMap<View, Animator> currentAnimCopy =
    927                             (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
    928                     for (Animator anim : currentAnimCopy.values()) {
    929                         anim.end();
    930                     }
    931                     currentDisappearingAnimations.clear();
    932                 }
    933                 break;
    934         }
    935     }
    936 
    937     /**
    938      * This method runs the animation that makes an added item appear.
    939      *
    940      * @param parent The ViewGroup to which the View is being added.
    941      * @param child The View being added to the ViewGroup.
    942      */
    943     private void runAppearingTransition(final ViewGroup parent, final View child) {
    944         Animator currentAnimation = currentDisappearingAnimations.get(child);
    945         if (currentAnimation != null) {
    946             currentAnimation.cancel();
    947         }
    948         if (mAppearingAnim == null) {
    949             if (mListeners != null) {
    950                 for (TransitionListener listener : mListeners) {
    951                     listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
    952                 }
    953             }
    954             return;
    955         }
    956         Animator anim = mAppearingAnim.clone();
    957         anim.setTarget(child);
    958         anim.setStartDelay(mAppearingDelay);
    959         anim.setDuration(mAppearingDuration);
    960         if (anim instanceof ObjectAnimator) {
    961             ((ObjectAnimator) anim).setCurrentPlayTime(0);
    962         }
    963         if (mListeners != null) {
    964             anim.addListener(new AnimatorListenerAdapter() {
    965                 @Override
    966                 public void onAnimationEnd(Animator anim) {
    967                     currentAppearingAnimations.remove(child);
    968                     for (TransitionListener listener : mListeners) {
    969                         listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
    970                     }
    971                 }
    972             });
    973         }
    974         currentAppearingAnimations.put(child, anim);
    975         anim.start();
    976     }
    977 
    978     /**
    979      * This method runs the animation that makes a removed item disappear.
    980      *
    981      * @param parent The ViewGroup from which the View is being removed.
    982      * @param child The View being removed from the ViewGroup.
    983      */
    984     private void runDisappearingTransition(final ViewGroup parent, final View child) {
    985         Animator currentAnimation = currentAppearingAnimations.get(child);
    986         if (currentAnimation != null) {
    987             currentAnimation.cancel();
    988         }
    989         if (mDisappearingAnim == null) {
    990             if (mListeners != null) {
    991                 for (TransitionListener listener : mListeners) {
    992                     listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
    993                 }
    994             }
    995             return;
    996         }
    997         Animator anim = mDisappearingAnim.clone();
    998         anim.setStartDelay(mDisappearingDelay);
    999         anim.setDuration(mDisappearingDuration);
   1000         anim.setTarget(child);
   1001         if (mListeners != null) {
   1002             anim.addListener(new AnimatorListenerAdapter() {
   1003                 @Override
   1004                 public void onAnimationEnd(Animator anim) {
   1005                     currentDisappearingAnimations.remove(child);
   1006                     for (TransitionListener listener : mListeners) {
   1007                         listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
   1008                     }
   1009                 }
   1010             });
   1011         }
   1012         if (anim instanceof ObjectAnimator) {
   1013             ((ObjectAnimator) anim).setCurrentPlayTime(0);
   1014         }
   1015         currentDisappearingAnimations.put(child, anim);
   1016         anim.start();
   1017     }
   1018 
   1019     /**
   1020      * This method is called by ViewGroup when a child view is about to be added to the
   1021      * container. This callback starts the process of a transition; we grab the starting
   1022      * values, listen for changes to all of the children of the container, and start appropriate
   1023      * animations.
   1024      *
   1025      * @param parent The ViewGroup to which the View is being added.
   1026      * @param child The View being added to the ViewGroup.
   1027      */
   1028     public void addChild(ViewGroup parent, View child) {
   1029         // Want disappearing animations to finish up before proceeding
   1030         cancel(DISAPPEARING);
   1031         // Also, cancel changing animations so that we start fresh ones from current locations
   1032         cancel(CHANGE_APPEARING);
   1033         if (mListeners != null) {
   1034             for (TransitionListener listener : mListeners) {
   1035                 listener.startTransition(this, parent, child, APPEARING);
   1036             }
   1037         }
   1038         runChangeTransition(parent, child, APPEARING);
   1039         runAppearingTransition(parent, child);
   1040     }
   1041 
   1042     /**
   1043      * This method is called by ViewGroup when a child view is about to be added to the
   1044      * container. This callback starts the process of a transition; we grab the starting
   1045      * values, listen for changes to all of the children of the container, and start appropriate
   1046      * animations.
   1047      *
   1048      * @param parent The ViewGroup to which the View is being added.
   1049      * @param child The View being added to the ViewGroup.
   1050      */
   1051     public void showChild(ViewGroup parent, View child) {
   1052         addChild(parent, child);
   1053     }
   1054 
   1055     /**
   1056      * This method is called by ViewGroup when a child view is about to be removed from the
   1057      * container. This callback starts the process of a transition; we grab the starting
   1058      * values, listen for changes to all of the children of the container, and start appropriate
   1059      * animations.
   1060      *
   1061      * @param parent The ViewGroup from which the View is being removed.
   1062      * @param child The View being removed from the ViewGroup.
   1063      */
   1064     public void removeChild(ViewGroup parent, View child) {
   1065         // Want appearing animations to finish up before proceeding
   1066         cancel(APPEARING);
   1067         // Also, cancel changing animations so that we start fresh ones from current locations
   1068         cancel(CHANGE_DISAPPEARING);
   1069         if (mListeners != null) {
   1070             for (TransitionListener listener : mListeners) {
   1071                 listener.startTransition(this, parent, child, DISAPPEARING);
   1072             }
   1073         }
   1074         runChangeTransition(parent, child, DISAPPEARING);
   1075         runDisappearingTransition(parent, child);
   1076     }
   1077 
   1078     /**
   1079      * This method is called by ViewGroup when a child view is about to be removed from the
   1080      * container. This callback starts the process of a transition; we grab the starting
   1081      * values, listen for changes to all of the children of the container, and start appropriate
   1082      * animations.
   1083      *
   1084      * @param parent The ViewGroup from which the View is being removed.
   1085      * @param child The View being removed from the ViewGroup.
   1086      */
   1087     public void hideChild(ViewGroup parent, View child) {
   1088         removeChild(parent, child);
   1089     }
   1090 
   1091     /**
   1092      * Add a listener that will be called when the bounds of the view change due to
   1093      * layout processing.
   1094      *
   1095      * @param listener The listener that will be called when layout bounds change.
   1096      */
   1097     public void addTransitionListener(TransitionListener listener) {
   1098         if (mListeners == null) {
   1099             mListeners = new ArrayList<TransitionListener>();
   1100         }
   1101         mListeners.add(listener);
   1102     }
   1103 
   1104     /**
   1105      * Remove a listener for layout changes.
   1106      *
   1107      * @param listener The listener for layout bounds change.
   1108      */
   1109     public void removeTransitionListener(TransitionListener listener) {
   1110         if (mListeners == null) {
   1111             return;
   1112         }
   1113         mListeners.remove(listener);
   1114     }
   1115 
   1116     /**
   1117      * Gets the current list of listeners for layout changes.
   1118      * @return
   1119      */
   1120     public List<TransitionListener> getTransitionListeners() {
   1121         return mListeners;
   1122     }
   1123 
   1124     /**
   1125      * This interface is used for listening to starting and ending events for transitions.
   1126      */
   1127     public interface TransitionListener {
   1128 
   1129         /**
   1130          * This event is sent to listeners when any type of transition animation begins.
   1131          *
   1132          * @param transition The LayoutTransition sending out the event.
   1133          * @param container The ViewGroup on which the transition is playing.
   1134          * @param view The View object being affected by the transition animation.
   1135          * @param transitionType The type of transition that is beginning,
   1136          * {@link android.animation.LayoutTransition#APPEARING},
   1137          * {@link android.animation.LayoutTransition#DISAPPEARING},
   1138          * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
   1139          * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
   1140          */
   1141         public void startTransition(LayoutTransition transition, ViewGroup container,
   1142                 View view, int transitionType);
   1143 
   1144         /**
   1145          * This event is sent to listeners when any type of transition animation ends.
   1146          *
   1147          * @param transition The LayoutTransition sending out the event.
   1148          * @param container The ViewGroup on which the transition is playing.
   1149          * @param view The View object being affected by the transition animation.
   1150          * @param transitionType The type of transition that is ending,
   1151          * {@link android.animation.LayoutTransition#APPEARING},
   1152          * {@link android.animation.LayoutTransition#DISAPPEARING},
   1153          * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
   1154          * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
   1155          */
   1156         public void endTransition(LayoutTransition transition, ViewGroup container,
   1157                 View view, int transitionType);
   1158     }
   1159 
   1160 }
   1161