• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
LayoutTransition()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      */
setDuration(long duration)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      */
setStartDelay(int transitionType, long delay)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      */
getStartDelay(int transitionType)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      */
setDuration(int transitionType, long duration)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      */
getDuration(int transitionType)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      */
setStagger(int transitionType, long duration)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      */
getStagger(int transitionType)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      */
setInterpolator(int transitionType, TimeInterpolator interpolator)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      */
getInterpolator(int transitionType)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      */
setAnimator(int transitionType, Animator animator)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      */
getAnimator(int transitionType)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      */
runChangeTransition(final ViewGroup parent, View newView, final int changeReason)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      */
setAnimateParentHierarchy(boolean animateParentHierarchy)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      */
setupChangeAnimation(final ViewGroup parent, final int changeReason, Animator baseAnimator, final long duration, final View child)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      */
startChangingAnimations()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      */
endChangingAnimations()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      */
isChangingLayout()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      */
isRunning()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      */
cancel()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      */
cancel(int transitionType)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      */
runAppearingTransition(final ViewGroup parent, final View child)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      */
runDisappearingTransition(final ViewGroup parent, final View child)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      */
addChild(ViewGroup parent, View child)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      */
showChild(ViewGroup parent, View child)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      */
removeChild(ViewGroup parent, View child)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      */
hideChild(ViewGroup parent, View child)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      */
addTransitionListener(TransitionListener listener)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      */
removeTransitionListener(TransitionListener listener)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      */
getTransitionListeners()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          */
startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)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          */
endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType)1147         public void endTransition(LayoutTransition transition, ViewGroup container,
1148                 View view, int transitionType);
1149     }
1150 
1151 }
1152