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