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