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