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