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