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