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