Transition.java revision fe361d2113b8f3c54797d7bd720ca739328bd7aa
1/*
2 * Copyright (C) 2013 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.transition;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.TimeInterpolator;
22import android.content.Context;
23import android.content.res.TypedArray;
24import android.graphics.Path;
25import android.graphics.Rect;
26import android.util.ArrayMap;
27import android.util.AttributeSet;
28import android.util.Log;
29import android.util.LongSparseArray;
30import android.util.SparseArray;
31import android.util.SparseLongArray;
32import android.view.InflateException;
33import android.view.SurfaceView;
34import android.view.TextureView;
35import android.view.View;
36import android.view.ViewGroup;
37import android.view.ViewOverlay;
38import android.view.WindowId;
39import android.view.animation.AnimationUtils;
40import android.widget.ListView;
41import android.widget.Spinner;
42
43import java.util.ArrayList;
44import java.util.List;
45import java.util.StringTokenizer;
46
47import com.android.internal.R;
48
49/**
50 * A Transition holds information about animations that will be run on its
51 * targets during a scene change. Subclasses of this abstract class may
52 * choreograph several child transitions ({@link TransitionSet} or they may
53 * perform custom animations themselves. Any Transition has two main jobs:
54 * (1) capture property values, and (2) play animations based on changes to
55 * captured property values. A custom transition knows what property values
56 * on View objects are of interest to it, and also knows how to animate
57 * changes to those values. For example, the {@link Fade} transition tracks
58 * changes to visibility-related properties and is able to construct and run
59 * animations that fade items in or out based on changes to those properties.
60 *
61 * <p>Note: Transitions may not work correctly with either {@link SurfaceView}
62 * or {@link TextureView}, due to the way that these views are displayed
63 * on the screen. For SurfaceView, the problem is that the view is updated from
64 * a non-UI thread, so changes to the view due to transitions (such as moving
65 * and resizing the view) may be out of sync with the display inside those bounds.
66 * TextureView is more compatible with transitions in general, but some
67 * specific transitions (such as {@link Fade}) may not be compatible
68 * with TextureView because they rely on {@link ViewOverlay} functionality,
69 * which does not currently work with TextureView.</p>
70 *
71 * <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
72 * directory. Transition resources consist of a tag name for one of the Transition
73 * subclasses along with attributes to define some of the attributes of that transition.
74 * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:
75 *
76 * {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds}
77 *
78 * <p>This TransitionSet contains {@link android.transition.Explode} for visibility,
79 * {@link android.transition.ChangeBounds}, {@link android.transition.ChangeTransform},
80 * and {@link android.transition.ChangeClipBounds} and
81 * {@link android.transition.ChangeImageTransform}:</p>
82 *
83 * {@sample development/samples/ApiDemos/res/transition/explode_move_together.xml MultipleTransform}
84 *
85 * <p>Custom transition classes may be instantiated with a <code>transition</code> tag:</p>
86 * <pre>&lt;transition class="my.app.transition.CustomTransition"/></pre>
87 * <p>Custom transition classes loaded from XML must have a public nullary (no argument)
88 * constructor.</p>
89 *
90 * <p>Note that attributes for the transition are not required, just as they are
91 * optional when declared in code; Transitions created from XML resources will use
92 * the same defaults as their code-created equivalents. Here is a slightly more
93 * elaborate example which declares a {@link TransitionSet} transition with
94 * {@link ChangeBounds} and {@link Fade} child transitions:</p>
95 *
96 * {@sample
97 * development/samples/ApiDemos/res/transition/changebounds_fadeout_sequential.xml TransitionSet}
98 *
99 * <p>In this example, the transitionOrdering attribute is used on the TransitionSet
100 * object to change from the default {@link TransitionSet#ORDERING_TOGETHER} behavior
101 * to be {@link TransitionSet#ORDERING_SEQUENTIAL} instead. Also, the {@link Fade}
102 * transition uses a fadingMode of {@link Fade#OUT} instead of the default
103 * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which
104 * takes a set of {@link android.R.styleable#TransitionTarget target} tags, each
105 * of which lists a specific <code>targetId</code>, <code>targetClass</code>,
106 * <code>targetName</code>, <code>excludeId</code>, <code>excludeClass</code>, or
107 * <code>excludeName</code>, which this transition acts upon.
108 * Use of targets is optional, but can be used to either limit the time spent checking
109 * attributes on unchanging views, or limiting the types of animations run on specific views.
110 * In this case, we know that only the <code>grayscaleContainer</code> will be
111 * disappearing, so we choose to limit the {@link Fade} transition to only that view.</p>
112 *
113 * Further information on XML resource descriptions for transitions can be found for
114 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
115 * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade}, and
116 * {@link android.R.styleable#Slide}.
117 *
118 */
119public abstract class Transition implements Cloneable {
120
121    private static final String LOG_TAG = "Transition";
122    static final boolean DBG = false;
123
124    /**
125     * With {@link #setMatchOrder(int...)}, chooses to match by View instance.
126     */
127    public static final int MATCH_INSTANCE = 0x1;
128    private static final int MATCH_FIRST = MATCH_INSTANCE;
129
130    /**
131     * With {@link #setMatchOrder(int...)}, chooses to match by
132     * {@link android.view.View#getTransitionName()}. Null names will not be matched.
133     */
134    public static final int MATCH_NAME = 0x2;
135
136    /**
137     * With {@link #setMatchOrder(int...)}, chooses to match by
138     * {@link android.view.View#getId()}. Negative IDs will not be matched.
139     */
140    public static final int MATCH_ID = 0x3;
141
142    /**
143     * With {@link #setMatchOrder(int...)}, chooses to match by the {@link android.widget.Adapter}
144     * item id. When {@link android.widget.Adapter#hasStableIds()} returns false, no match
145     * will be made for items.
146     */
147    public static final int MATCH_ITEM_ID = 0x4;
148
149    private static final int MATCH_LAST = MATCH_ITEM_ID;
150
151    private static final String MATCH_INSTANCE_STR = "instance";
152    private static final String MATCH_NAME_STR = "name";
153    /** To be removed before L release */
154    private static final String MATCH_VIEW_NAME_STR = "viewName";
155    private static final String MATCH_ID_STR = "id";
156    private static final String MATCH_ITEM_ID_STR = "itemId";
157
158    private static final int[] DEFAULT_MATCH_ORDER = {
159        MATCH_NAME,
160        MATCH_INSTANCE,
161        MATCH_ID,
162        MATCH_ITEM_ID,
163    };
164
165    private static final PathMotion STRAIGHT_PATH_MOTION = new PathMotion() {
166        @Override
167        public Path getPath(float startX, float startY, float endX, float endY) {
168            Path path = new Path();
169            path.moveTo(startX, startY);
170            path.lineTo(endX, endY);
171            return path;
172        }
173    };
174
175    private String mName = getClass().getName();
176
177    long mStartDelay = -1;
178    long mDuration = -1;
179    TimeInterpolator mInterpolator = null;
180    ArrayList<Integer> mTargetIds = new ArrayList<Integer>();
181    ArrayList<View> mTargets = new ArrayList<View>();
182    ArrayList<String> mTargetNames = null;
183    ArrayList<Class> mTargetTypes = null;
184    ArrayList<Integer> mTargetIdExcludes = null;
185    ArrayList<View> mTargetExcludes = null;
186    ArrayList<Class> mTargetTypeExcludes = null;
187    ArrayList<String> mTargetNameExcludes = null;
188    ArrayList<Integer> mTargetIdChildExcludes = null;
189    ArrayList<View> mTargetChildExcludes = null;
190    ArrayList<Class> mTargetTypeChildExcludes = null;
191    private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
192    private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
193    TransitionSet mParent = null;
194    private int[] mMatchOrder = DEFAULT_MATCH_ORDER;
195
196    // Per-animator information used for later canceling when future transitions overlap
197    private static ThreadLocal<ArrayMap<Animator, AnimationInfo>> sRunningAnimators =
198            new ThreadLocal<ArrayMap<Animator, AnimationInfo>>();
199
200    // Scene Root is set at createAnimator() time in the cloned Transition
201    ViewGroup mSceneRoot = null;
202
203    // Whether removing views from their parent is possible. This is only for views
204    // in the start scene, which are no longer in the view hierarchy. This property
205    // is determined by whether the previous Scene was created from a layout
206    // resource, and thus the views from the exited scene are going away anyway
207    // and can be removed as necessary to achieve a particular effect, such as
208    // removing them from parents to add them to overlays.
209    boolean mCanRemoveViews = false;
210
211    // Track all animators in use in case the transition gets canceled and needs to
212    // cancel running animators
213    private ArrayList<Animator> mCurrentAnimators = new ArrayList<Animator>();
214
215    // Number of per-target instances of this Transition currently running. This count is
216    // determined by calls to start() and end()
217    int mNumInstances = 0;
218
219    // Whether this transition is currently paused, due to a call to pause()
220    boolean mPaused = false;
221
222    // Whether this transition has ended. Used to avoid pause/resume on transitions
223    // that have completed
224    private boolean mEnded = false;
225
226    // The set of listeners to be sent transition lifecycle events.
227    ArrayList<TransitionListener> mListeners = null;
228
229    // The set of animators collected from calls to createAnimator(),
230    // to be run in runAnimators()
231    ArrayList<Animator> mAnimators = new ArrayList<Animator>();
232
233    // The function for calculating the Animation start delay.
234    TransitionPropagation mPropagation;
235
236    // The rectangular region for Transitions like Explode and TransitionPropagations
237    // like CircularPropagation
238    EpicenterCallback mEpicenterCallback;
239
240    // For Fragment shared element transitions, linking views explicitly by mismatching
241    // transitionNames.
242    ArrayMap<String, String> mNameOverrides;
243
244    // The function used to interpolate along two-dimensional points. Typically used
245    // for adding curves to x/y View motion.
246    private PathMotion mPathMotion = STRAIGHT_PATH_MOTION;
247
248    /**
249     * Constructs a Transition object with no target objects. A transition with
250     * no targets defaults to running on all target objects in the scene hierarchy
251     * (if the transition is not contained in a TransitionSet), or all target
252     * objects passed down from its parent (if it is in a TransitionSet).
253     */
254    public Transition() {}
255
256    /**
257     * Perform inflation from XML and apply a class-specific base style from a
258     * theme attribute or style resource. This constructor of Transition allows
259     * subclasses to use their own base style when they are inflating.
260     *
261     * @param context The Context the transition is running in, through which it can
262     *        access the current theme, resources, etc.
263     * @param attrs The attributes of the XML tag that is inflating the transition.
264     */
265    public Transition(Context context, AttributeSet attrs) {
266
267        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Transition);
268        long duration = a.getInt(R.styleable.Transition_duration, -1);
269        if (duration >= 0) {
270            setDuration(duration);
271        }
272        long startDelay = a.getInt(R.styleable.Transition_startDelay, -1);
273        if (startDelay > 0) {
274            setStartDelay(startDelay);
275        }
276        final int resID = a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
277        if (resID > 0) {
278            setInterpolator(AnimationUtils.loadInterpolator(context, resID));
279        }
280        String matchOrder = a.getString(R.styleable.Transition_matchOrder);
281        if (matchOrder != null) {
282            setMatchOrder(parseMatchOrder(matchOrder));
283        }
284        a.recycle();
285    }
286
287    private static int[] parseMatchOrder(String matchOrderString) {
288        StringTokenizer st = new StringTokenizer(matchOrderString, ",");
289        int matches[] = new int[st.countTokens()];
290        int index = 0;
291        while (st.hasMoreTokens()) {
292            String token = st.nextToken().trim();
293            if (MATCH_ID_STR.equalsIgnoreCase(token)) {
294                matches[index] = Transition.MATCH_ID;
295            } else if (MATCH_INSTANCE_STR.equalsIgnoreCase(token)) {
296                matches[index] = Transition.MATCH_INSTANCE;
297            } else if (MATCH_NAME_STR.equalsIgnoreCase(token)) {
298                matches[index] = Transition.MATCH_NAME;
299            } else if (MATCH_VIEW_NAME_STR.equalsIgnoreCase(token)) {
300                matches[index] = Transition.MATCH_NAME;
301            } else if (MATCH_ITEM_ID_STR.equalsIgnoreCase(token)) {
302                matches[index] = Transition.MATCH_ITEM_ID;
303            } else if (token.isEmpty()) {
304                int[] smallerMatches = new int[matches.length - 1];
305                System.arraycopy(matches, 0, smallerMatches, 0, index);
306                matches = smallerMatches;
307                index--;
308            } else {
309                throw new InflateException("Unknown match type in matchOrder: '" + token + "'");
310            }
311            index++;
312        }
313        return matches;
314    }
315
316    /**
317     * Sets the duration of this transition. By default, there is no duration
318     * (indicated by a negative number), which means that the Animator created by
319     * the transition will have its own specified duration. If the duration of a
320     * Transition is set, that duration will override the Animator duration.
321     *
322     * @param duration The length of the animation, in milliseconds.
323     * @return This transition object.
324     * @attr ref android.R.styleable#Transition_duration
325     */
326    public Transition setDuration(long duration) {
327        mDuration = duration;
328        return this;
329    }
330
331    /**
332     * Returns the duration set on this transition. If no duration has been set,
333     * the returned value will be negative, indicating that resulting animators will
334     * retain their own durations.
335     *
336     * @return The duration set on this transition, in milliseconds, if one has been
337     * set, otherwise returns a negative number.
338     */
339    public long getDuration() {
340        return mDuration;
341    }
342
343    /**
344     * Sets the startDelay of this transition. By default, there is no delay
345     * (indicated by a negative number), which means that the Animator created by
346     * the transition will have its own specified startDelay. If the delay of a
347     * Transition is set, that delay will override the Animator delay.
348     *
349     * @param startDelay The length of the delay, in milliseconds.
350     * @return This transition object.
351     * @attr ref android.R.styleable#Transition_startDelay
352     */
353    public Transition setStartDelay(long startDelay) {
354        mStartDelay = startDelay;
355        return this;
356    }
357
358    /**
359     * Returns the startDelay set on this transition. If no startDelay has been set,
360     * the returned value will be negative, indicating that resulting animators will
361     * retain their own startDelays.
362     *
363     * @return The startDelay set on this transition, in milliseconds, if one has
364     * been set, otherwise returns a negative number.
365     */
366    public long getStartDelay() {
367        return mStartDelay;
368    }
369
370    /**
371     * Sets the interpolator of this transition. By default, the interpolator
372     * is null, which means that the Animator created by the transition
373     * will have its own specified interpolator. If the interpolator of a
374     * Transition is set, that interpolator will override the Animator interpolator.
375     *
376     * @param interpolator The time interpolator used by the transition
377     * @return This transition object.
378     * @attr ref android.R.styleable#Transition_interpolator
379     */
380    public Transition setInterpolator(TimeInterpolator interpolator) {
381        mInterpolator = interpolator;
382        return this;
383    }
384
385    /**
386     * Returns the interpolator set on this transition. If no interpolator has been set,
387     * the returned value will be null, indicating that resulting animators will
388     * retain their own interpolators.
389     *
390     * @return The interpolator set on this transition, if one has been set, otherwise
391     * returns null.
392     */
393    public TimeInterpolator getInterpolator() {
394        return mInterpolator;
395    }
396
397    /**
398     * Returns the set of property names used stored in the {@link TransitionValues}
399     * object passed into {@link #captureStartValues(TransitionValues)} that
400     * this transition cares about for the purposes of canceling overlapping animations.
401     * When any transition is started on a given scene root, all transitions
402     * currently running on that same scene root are checked to see whether the
403     * properties on which they based their animations agree with the end values of
404     * the same properties in the new transition. If the end values are not equal,
405     * then the old animation is canceled since the new transition will start a new
406     * animation to these new values. If the values are equal, the old animation is
407     * allowed to continue and no new animation is started for that transition.
408     *
409     * <p>A transition does not need to override this method. However, not doing so
410     * will mean that the cancellation logic outlined in the previous paragraph
411     * will be skipped for that transition, possibly leading to artifacts as
412     * old transitions and new transitions on the same targets run in parallel,
413     * animating views toward potentially different end values.</p>
414     *
415     * @return An array of property names as described in the class documentation for
416     * {@link TransitionValues}. The default implementation returns <code>null</code>.
417     */
418    public String[] getTransitionProperties() {
419        return null;
420    }
421
422    /**
423     * This method creates an animation that will be run for this transition
424     * given the information in the startValues and endValues structures captured
425     * earlier for the start and end scenes. Subclasses of Transition should override
426     * this method. The method should only be called by the transition system; it is
427     * not intended to be called from external classes.
428     *
429     * <p>This method is called by the transition's parent (all the way up to the
430     * topmost Transition in the hierarchy) with the sceneRoot and start/end
431     * values that the transition may need to set up initial target values
432     * and construct an appropriate animation. For example, if an overall
433     * Transition is a {@link TransitionSet} consisting of several
434     * child transitions in sequence, then some of the child transitions may
435     * want to set initial values on target views prior to the overall
436     * Transition commencing, to put them in an appropriate state for the
437     * delay between that start and the child Transition start time. For
438     * example, a transition that fades an item in may wish to set the starting
439     * alpha value to 0, to avoid it blinking in prior to the transition
440     * actually starting the animation. This is necessary because the scene
441     * change that triggers the Transition will automatically set the end-scene
442     * on all target views, so a Transition that wants to animate from a
443     * different value should set that value prior to returning from this method.</p>
444     *
445     * <p>Additionally, a Transition can perform logic to determine whether
446     * the transition needs to run on the given target and start/end values.
447     * For example, a transition that resizes objects on the screen may wish
448     * to avoid running for views which are not present in either the start
449     * or end scenes.</p>
450     *
451     * <p>If there is an animator created and returned from this method, the
452     * transition mechanism will apply any applicable duration, startDelay,
453     * and interpolator to that animation and start it. A return value of
454     * <code>null</code> indicates that no animation should run. The default
455     * implementation returns null.</p>
456     *
457     * <p>The method is called for every applicable target object, which is
458     * stored in the {@link TransitionValues#view} field.</p>
459     *
460     *
461     * @param sceneRoot The root of the transition hierarchy.
462     * @param startValues The values for a specific target in the start scene.
463     * @param endValues The values for the target in the end scene.
464     * @return A Animator to be started at the appropriate time in the
465     * overall transition for this scene change. A null value means no animation
466     * should be run.
467     */
468    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
469            TransitionValues endValues) {
470        return null;
471    }
472
473    /**
474     * Sets the order in which Transition matches View start and end values.
475     * <p>
476     * The default behavior is to match first by {@link android.view.View#getTransitionName()},
477     * then by View instance, then by {@link android.view.View#getId()} and finally
478     * by its item ID if it is in a direct child of ListView. The caller can
479     * choose to have only some or all of the values of {@link #MATCH_INSTANCE},
480     * {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. Only
481     * the match algorithms supplied will be used to determine whether Views are the
482     * the same in both the start and end Scene. Views that do not match will be considered
483     * as entering or leaving the Scene.
484     * </p>
485     * @param matches A list of zero or more of {@link #MATCH_INSTANCE},
486     *                {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}.
487     *                If none are provided, then the default match order will be set.
488     */
489    public void setMatchOrder(int... matches) {
490        if (matches == null || matches.length == 0) {
491            mMatchOrder = DEFAULT_MATCH_ORDER;
492        } else {
493            for (int i = 0; i < matches.length; i++) {
494                int match = matches[i];
495                if (!isValidMatch(match)) {
496                    throw new IllegalArgumentException("matches contains invalid value");
497                }
498                if (alreadyContains(matches, i)) {
499                    throw new IllegalArgumentException("matches contains a duplicate value");
500                }
501            }
502            mMatchOrder = matches.clone();
503        }
504    }
505
506    private static boolean isValidMatch(int match) {
507        return (match >= MATCH_FIRST && match <= MATCH_LAST);
508    }
509
510    private static boolean alreadyContains(int[] array, int searchIndex) {
511        int value = array[searchIndex];
512        for (int i = 0; i < searchIndex; i++) {
513            if (array[i] == value) {
514                return true;
515            }
516        }
517        return false;
518    }
519
520    /**
521     * Match start/end values by View instance. Adds matched values to startValuesList
522     * and endValuesList and removes them from unmatchedStart and unmatchedEnd.
523     */
524    private void matchInstances(ArrayList<TransitionValues> startValuesList,
525            ArrayList<TransitionValues> endValuesList,
526            ArrayMap<View, TransitionValues> unmatchedStart,
527            ArrayMap<View, TransitionValues> unmatchedEnd) {
528        for (int i = unmatchedStart.size() - 1; i >= 0; i--) {
529            View view = unmatchedStart.keyAt(i);
530            TransitionValues end = unmatchedEnd.remove(view);
531            if (end != null) {
532                TransitionValues start = unmatchedStart.removeAt(i);
533                startValuesList.add(start);
534                endValuesList.add(end);
535            }
536        }
537    }
538
539    /**
540     * Match start/end values by Adapter item ID. Adds matched values to startValuesList
541     * and endValuesList and removes them from unmatchedStart and unmatchedEnd, using
542     * startItemIds and endItemIds as a guide for which Views have unique item IDs.
543     */
544    private void matchItemIds(ArrayList<TransitionValues> startValuesList,
545            ArrayList<TransitionValues> endValuesList,
546            ArrayMap<View, TransitionValues> unmatchedStart,
547            ArrayMap<View, TransitionValues> unmatchedEnd,
548            LongSparseArray<View> startItemIds, LongSparseArray<View> endItemIds) {
549        int numStartIds = startItemIds.size();
550        for (int i = 0; i < numStartIds; i++) {
551            View startView = startItemIds.valueAt(i);
552            if (startView != null) {
553                View endView = endItemIds.get(startItemIds.keyAt(i));
554                if (endView != null) {
555                    TransitionValues startValues = unmatchedStart.get(startView);
556                    TransitionValues endValues = unmatchedEnd.get(endView);
557                    if (startValues != null && endValues != null) {
558                        startValuesList.add(startValues);
559                        endValuesList.add(endValues);
560                        unmatchedStart.remove(startView);
561                        unmatchedEnd.remove(endView);
562                    }
563                }
564            }
565        }
566    }
567
568    /**
569     * Match start/end values by Adapter view ID. Adds matched values to startValuesList
570     * and endValuesList and removes them from unmatchedStart and unmatchedEnd, using
571     * startIds and endIds as a guide for which Views have unique IDs.
572     */
573    private void matchIds(ArrayList<TransitionValues> startValuesList,
574            ArrayList<TransitionValues> endValuesList,
575            ArrayMap<View, TransitionValues> unmatchedStart,
576            ArrayMap<View, TransitionValues> unmatchedEnd,
577            SparseArray<View> startIds, SparseArray<View> endIds) {
578        int numStartIds = startIds.size();
579        for (int i = 0; i < numStartIds; i++) {
580            View startView = startIds.valueAt(i);
581            if (startView != null && isValidTarget(startView)) {
582                View endView = endIds.get(startIds.keyAt(i));
583                if (endView != null && isValidTarget(endView)) {
584                    TransitionValues startValues = unmatchedStart.get(startView);
585                    TransitionValues endValues = unmatchedEnd.get(endView);
586                    if (startValues != null && endValues != null) {
587                        startValuesList.add(startValues);
588                        endValuesList.add(endValues);
589                        unmatchedStart.remove(startView);
590                        unmatchedEnd.remove(endView);
591                    }
592                }
593            }
594        }
595    }
596
597    /**
598     * Match start/end values by Adapter transitionName. Adds matched values to startValuesList
599     * and endValuesList and removes them from unmatchedStart and unmatchedEnd, using
600     * startNames and endNames as a guide for which Views have unique transitionNames.
601     */
602    private void matchNames(ArrayList<TransitionValues> startValuesList,
603            ArrayList<TransitionValues> endValuesList,
604            ArrayMap<View, TransitionValues> unmatchedStart,
605            ArrayMap<View, TransitionValues> unmatchedEnd,
606            ArrayMap<String, View> startNames, ArrayMap<String, View> endNames) {
607        int numStartNames = startNames.size();
608        for (int i = 0; i < numStartNames; i++) {
609            View startView = startNames.valueAt(i);
610            if (startView != null && isValidTarget(startView)) {
611                View endView = endNames.get(startNames.keyAt(i));
612                if (endView != null && isValidTarget(endView)) {
613                    TransitionValues startValues = unmatchedStart.get(startView);
614                    TransitionValues endValues = unmatchedEnd.get(endView);
615                    if (startValues != null && endValues != null) {
616                        startValuesList.add(startValues);
617                        endValuesList.add(endValues);
618                        unmatchedStart.remove(startView);
619                        unmatchedEnd.remove(endView);
620                    }
621                }
622            }
623        }
624    }
625
626    /**
627     * Adds all values from unmatchedStart and unmatchedEnd to startValuesList and endValuesList,
628     * assuming that there is no match between values in the list.
629     */
630    private void addUnmatched(ArrayList<TransitionValues> startValuesList,
631            ArrayList<TransitionValues> endValuesList,
632            ArrayMap<View, TransitionValues> unmatchedStart,
633            ArrayMap<View, TransitionValues> unmatchedEnd) {
634        // Views that only exist in the start Scene
635        for (int i = 0; i < unmatchedStart.size(); i++) {
636            startValuesList.add(unmatchedStart.valueAt(i));
637            endValuesList.add(null);
638        }
639
640        // Views that only exist in the end Scene
641        for (int i = 0; i < unmatchedEnd.size(); i++) {
642            endValuesList.add(unmatchedEnd.valueAt(i));
643            startValuesList.add(null);
644        }
645    }
646
647    private void matchStartAndEnd(TransitionValuesMaps startValues,
648            TransitionValuesMaps endValues,
649            ArrayList<TransitionValues> startValuesList,
650            ArrayList<TransitionValues> endValuesList) {
651        ArrayMap<View, TransitionValues> unmatchedStart =
652                new ArrayMap<View, TransitionValues>(startValues.viewValues);
653        ArrayMap<View, TransitionValues> unmatchedEnd =
654                new ArrayMap<View, TransitionValues>(endValues.viewValues);
655
656        for (int i = 0; i < mMatchOrder.length; i++) {
657            switch (mMatchOrder[i]) {
658                case MATCH_INSTANCE:
659                    matchInstances(startValuesList, endValuesList, unmatchedStart, unmatchedEnd);
660                    break;
661                case MATCH_NAME:
662                    matchNames(startValuesList, endValuesList, unmatchedStart, unmatchedEnd,
663                            startValues.nameValues, endValues.nameValues);
664                    break;
665                case MATCH_ID:
666                    matchIds(startValuesList, endValuesList, unmatchedStart, unmatchedEnd,
667                            startValues.idValues, endValues.idValues);
668                    break;
669                case MATCH_ITEM_ID:
670                    matchItemIds(startValuesList, endValuesList, unmatchedStart, unmatchedEnd,
671                            startValues.itemIdValues, endValues.itemIdValues);
672                    break;
673            }
674        }
675        addUnmatched(startValuesList, endValuesList, unmatchedStart, unmatchedEnd);
676    }
677
678    /**
679     * This method, essentially a wrapper around all calls to createAnimator for all
680     * possible target views, is called with the entire set of start/end
681     * values. The implementation in Transition iterates through these lists
682     * and calls {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
683     * with each set of start/end values on this transition. The
684     * TransitionSet subclass overrides this method and delegates it to
685     * each of its children in succession.
686     *
687     * @hide
688     */
689    protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
690            TransitionValuesMaps endValues) {
691        if (DBG) {
692            Log.d(LOG_TAG, "createAnimators() for " + this);
693        }
694        ArrayList<TransitionValues> startValuesList = new ArrayList<TransitionValues>();
695        ArrayList<TransitionValues> endValuesList = new ArrayList<TransitionValues>();
696        matchStartAndEnd(startValues, endValues, startValuesList, endValuesList);
697
698        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
699        long minStartDelay = Long.MAX_VALUE;
700        int minAnimator = mAnimators.size();
701        SparseLongArray startDelays = new SparseLongArray();
702        for (int i = 0; i < startValuesList.size(); ++i) {
703            TransitionValues start = startValuesList.get(i);
704            TransitionValues end = endValuesList.get(i);
705            // Only bother trying to animate with valid values that differ between start/end
706            boolean isInvalidStart = start != null && !isValidTarget(start.view);
707            boolean isInvalidEnd = end != null && !isValidTarget(end.view);
708            boolean isChanged = start != end && (start == null || !start.equals(end));
709            if (isChanged && !isInvalidStart && !isInvalidEnd) {
710                if (DBG) {
711                    View view = (end != null) ? end.view : start.view;
712                    Log.d(LOG_TAG, "  differing start/end values for view " + view);
713                    if (start == null || end == null) {
714                        Log.d(LOG_TAG, "    " + ((start == null) ?
715                                "start null, end non-null" : "start non-null, end null"));
716                    } else {
717                        for (String key : start.values.keySet()) {
718                            Object startValue = start.values.get(key);
719                            Object endValue = end.values.get(key);
720                            if (startValue != endValue && !startValue.equals(endValue)) {
721                                Log.d(LOG_TAG, "    " + key + ": start(" + startValue +
722                                        "), end(" + endValue + ")");
723                            }
724                        }
725                    }
726                }
727                // TODO: what to do about targetIds and itemIds?
728                Animator animator = createAnimator(sceneRoot, start, end);
729                if (animator != null) {
730                    // Save animation info for future cancellation purposes
731                    View view = null;
732                    TransitionValues infoValues = null;
733                    if (end != null) {
734                        view = end.view;
735                        String[] properties = getTransitionProperties();
736                        if (view != null && properties != null && properties.length > 0) {
737                            infoValues = new TransitionValues();
738                            infoValues.view = view;
739                            TransitionValues newValues = endValues.viewValues.get(view);
740                            if (newValues != null) {
741                                for (int j = 0; j < properties.length; ++j) {
742                                    infoValues.values.put(properties[j],
743                                            newValues.values.get(properties[j]));
744                                }
745                            }
746                            int numExistingAnims = runningAnimators.size();
747                            for (int j = 0; j < numExistingAnims; ++j) {
748                                Animator anim = runningAnimators.keyAt(j);
749                                AnimationInfo info = runningAnimators.get(anim);
750                                if (info.values != null && info.view == view &&
751                                        ((info.name == null && getName() == null) ||
752                                                info.name.equals(getName()))) {
753                                    if (info.values.equals(infoValues)) {
754                                        // Favor the old animator
755                                        animator = null;
756                                        break;
757                                    }
758                                }
759                            }
760                        }
761                    } else {
762                        view = (start != null) ? start.view : null;
763                    }
764                    if (animator != null) {
765                        if (mPropagation != null) {
766                            long delay = mPropagation
767                                    .getStartDelay(sceneRoot, this, start, end);
768                            startDelays.put(mAnimators.size(), delay);
769                            minStartDelay = Math.min(delay, minStartDelay);
770                        }
771                        AnimationInfo info = new AnimationInfo(view, getName(), this,
772                                sceneRoot.getWindowId(), infoValues);
773                        runningAnimators.put(animator, info);
774                        mAnimators.add(animator);
775                    }
776                }
777            }
778        }
779        if (minStartDelay != 0) {
780            for (int i = 0; i < startDelays.size(); i++) {
781                int index = startDelays.keyAt(i);
782                Animator animator = mAnimators.get(index);
783                long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
784                animator.setStartDelay(delay);
785            }
786        }
787    }
788
789    /**
790     * Internal utility method for checking whether a given view/id
791     * is valid for this transition, where "valid" means that either
792     * the Transition has no target/targetId list (the default, in which
793     * cause the transition should act on all views in the hiearchy), or
794     * the given view is in the target list or the view id is in the
795     * targetId list. If the target parameter is null, then the target list
796     * is not checked (this is in the case of ListView items, where the
797     * views are ignored and only the ids are used).
798     */
799    boolean isValidTarget(View target) {
800        int targetId = target.getId();
801        if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) {
802            return false;
803        }
804        if (mTargetExcludes != null && mTargetExcludes.contains(target)) {
805            return false;
806        }
807        if (mTargetTypeExcludes != null && target != null) {
808            int numTypes = mTargetTypeExcludes.size();
809            for (int i = 0; i < numTypes; ++i) {
810                Class type = mTargetTypeExcludes.get(i);
811                if (type.isInstance(target)) {
812                    return false;
813                }
814            }
815        }
816        if (mTargetNameExcludes != null && target != null && target.getTransitionName() != null) {
817            if (mTargetNameExcludes.contains(target.getTransitionName())) {
818                return false;
819            }
820        }
821        if (mTargetIds.size() == 0 && mTargets.size() == 0 &&
822                (mTargetTypes == null || mTargetTypes.isEmpty() &&
823                (mTargetNames == null || mTargetNames.isEmpty()))) {
824            return true;
825        }
826        if (mTargetIds.contains(targetId) || mTargets.contains(target)) {
827            return true;
828        }
829        if (mTargetNames != null && mTargetNames.contains(target.getTransitionName())) {
830            return true;
831        }
832        if (mTargetTypes != null) {
833            for (int i = 0; i < mTargetTypes.size(); ++i) {
834                if (mTargetTypes.get(i).isInstance(target)) {
835                    return true;
836                }
837            }
838        }
839        return false;
840    }
841
842    private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() {
843        ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get();
844        if (runningAnimators == null) {
845            runningAnimators = new ArrayMap<Animator, AnimationInfo>();
846            sRunningAnimators.set(runningAnimators);
847        }
848        return runningAnimators;
849    }
850
851    /**
852     * This is called internally once all animations have been set up by the
853     * transition hierarchy.
854     *
855     * @hide
856     */
857    protected void runAnimators() {
858        if (DBG) {
859            Log.d(LOG_TAG, "runAnimators() on " + this);
860        }
861        start();
862        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
863        // Now start every Animator that was previously created for this transition
864        for (Animator anim : mAnimators) {
865            if (DBG) {
866                Log.d(LOG_TAG, "  anim: " + anim);
867            }
868            if (runningAnimators.containsKey(anim)) {
869                start();
870                runAnimator(anim, runningAnimators);
871            }
872        }
873        mAnimators.clear();
874        end();
875    }
876
877    private void runAnimator(Animator animator,
878            final ArrayMap<Animator, AnimationInfo> runningAnimators) {
879        if (animator != null) {
880            // TODO: could be a single listener instance for all of them since it uses the param
881            animator.addListener(new AnimatorListenerAdapter() {
882                @Override
883                public void onAnimationStart(Animator animation) {
884                    mCurrentAnimators.add(animation);
885                }
886                @Override
887                public void onAnimationEnd(Animator animation) {
888                    runningAnimators.remove(animation);
889                    mCurrentAnimators.remove(animation);
890                }
891            });
892            animate(animator);
893        }
894    }
895
896    /**
897     * Captures the values in the start scene for the properties that this
898     * transition monitors. These values are then passed as the startValues
899     * structure in a later call to
900     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
901     * The main concern for an implementation is what the
902     * properties are that the transition cares about and what the values are
903     * for all of those properties. The start and end values will be compared
904     * later during the
905     * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
906     * method to determine what, if any, animations, should be run.
907     *
908     * <p>Subclasses must implement this method. The method should only be called by the
909     * transition system; it is not intended to be called from external classes.</p>
910     *
911     * @param transitionValues The holder for any values that the Transition
912     * wishes to store. Values are stored in the <code>values</code> field
913     * of this TransitionValues object and are keyed from
914     * a String value. For example, to store a view's rotation value,
915     * a transition might call
916     * <code>transitionValues.values.put("appname:transitionname:rotation",
917     * view.getRotation())</code>. The target view will already be stored in
918     * the transitionValues structure when this method is called.
919     *
920     * @see #captureEndValues(TransitionValues)
921     * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
922     */
923    public abstract void captureStartValues(TransitionValues transitionValues);
924
925    /**
926     * Captures the values in the end scene for the properties that this
927     * transition monitors. These values are then passed as the endValues
928     * structure in a later call to
929     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
930     * The main concern for an implementation is what the
931     * properties are that the transition cares about and what the values are
932     * for all of those properties. The start and end values will be compared
933     * later during the
934     * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
935     * method to determine what, if any, animations, should be run.
936     *
937     * <p>Subclasses must implement this method. The method should only be called by the
938     * transition system; it is not intended to be called from external classes.</p>
939     *
940     * @param transitionValues The holder for any values that the Transition
941     * wishes to store. Values are stored in the <code>values</code> field
942     * of this TransitionValues object and are keyed from
943     * a String value. For example, to store a view's rotation value,
944     * a transition might call
945     * <code>transitionValues.values.put("appname:transitionname:rotation",
946     * view.getRotation())</code>. The target view will already be stored in
947     * the transitionValues structure when this method is called.
948     *
949     * @see #captureStartValues(TransitionValues)
950     * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
951     */
952    public abstract void captureEndValues(TransitionValues transitionValues);
953
954    /**
955     * Adds the id of a target view that this Transition is interested in
956     * animating. By default, there are no targetIds, and a Transition will
957     * listen for changes on every view in the hierarchy below the sceneRoot
958     * of the Scene being transitioned into. Setting targetIds constrains
959     * the Transition to only listen for, and act on, views with these IDs.
960     * Views with different IDs, or no IDs whatsoever, will be ignored.
961     *
962     * <p>Note that using ids to specify targets implies that ids should be unique
963     * within the view hierarchy underneat the scene root.</p>
964     *
965     * @see View#getId()
966     * @param targetId The id of a target view, must be a positive number.
967     * @return The Transition to which the targetId is added.
968     * Returning the same object makes it easier to chain calls during
969     * construction, such as
970     * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code>
971     */
972    public Transition addTarget(int targetId) {
973        if (targetId > 0) {
974            mTargetIds.add(targetId);
975        }
976        return this;
977    }
978
979    /**
980     * Adds the transitionName of a target view that this Transition is interested in
981     * animating. By default, there are no targetNames, and a Transition will
982     * listen for changes on every view in the hierarchy below the sceneRoot
983     * of the Scene being transitioned into. Setting targetNames constrains
984     * the Transition to only listen for, and act on, views with these transitionNames.
985     * Views with different transitionNames, or no transitionName whatsoever, will be ignored.
986     *
987     * <p>Note that transitionNames should be unique within the view hierarchy.</p>
988     *
989     * @see android.view.View#getTransitionName()
990     * @param targetName The transitionName of a target view, must be non-null.
991     * @return The Transition to which the target transitionName is added.
992     * Returning the same object makes it easier to chain calls during
993     * construction, such as
994     * <code>transitionSet.addTransitions(new Fade()).addTarget(someName);</code>
995     */
996    public Transition addTarget(String targetName) {
997        if (targetName != null) {
998            if (mTargetNames == null) {
999                mTargetNames = new ArrayList<String>();
1000            }
1001            mTargetNames.add(targetName);
1002        }
1003        return this;
1004    }
1005
1006    /**
1007     * Adds the Class of a target view that this Transition is interested in
1008     * animating. By default, there are no targetTypes, and a Transition will
1009     * listen for changes on every view in the hierarchy below the sceneRoot
1010     * of the Scene being transitioned into. Setting targetTypes constrains
1011     * the Transition to only listen for, and act on, views with these classes.
1012     * Views with different classes will be ignored.
1013     *
1014     * <p>Note that any View that can be cast to targetType will be included, so
1015     * if targetType is <code>View.class</code>, all Views will be included.</p>
1016     *
1017     * @see #addTarget(int)
1018     * @see #addTarget(android.view.View)
1019     * @see #excludeTarget(Class, boolean)
1020     * @see #excludeChildren(Class, boolean)
1021     *
1022     * @param targetType The type to include when running this transition.
1023     * @return The Transition to which the target class was added.
1024     * Returning the same object makes it easier to chain calls during
1025     * construction, such as
1026     * <code>transitionSet.addTransitions(new Fade()).addTarget(ImageView.class);</code>
1027     */
1028    public Transition addTarget(Class targetType) {
1029        if (targetType != null) {
1030            if (mTargetTypes == null) {
1031                mTargetTypes = new ArrayList<Class>();
1032            }
1033            mTargetTypes.add(targetType);
1034        }
1035        return this;
1036    }
1037
1038    /**
1039     * Removes the given targetId from the list of ids that this Transition
1040     * is interested in animating.
1041     *
1042     * @param targetId The id of a target view, must be a positive number.
1043     * @return The Transition from which the targetId is removed.
1044     * Returning the same object makes it easier to chain calls during
1045     * construction, such as
1046     * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code>
1047     */
1048    public Transition removeTarget(int targetId) {
1049        if (targetId > 0) {
1050            mTargetIds.remove(targetId);
1051        }
1052        return this;
1053    }
1054
1055    /**
1056     * Removes the given targetName from the list of transitionNames that this Transition
1057     * is interested in animating.
1058     *
1059     * @param targetName The transitionName of a target view, must not be null.
1060     * @return The Transition from which the targetName is removed.
1061     * Returning the same object makes it easier to chain calls during
1062     * construction, such as
1063     * <code>transitionSet.addTransitions(new Fade()).removeTargetName(someName);</code>
1064     */
1065    public Transition removeTarget(String targetName) {
1066        if (targetName != null && mTargetNames != null) {
1067            mTargetNames.remove(targetName);
1068        }
1069        return this;
1070    }
1071
1072    /**
1073     * Whether to add the given id to the list of target ids to exclude from this
1074     * transition. The <code>exclude</code> parameter specifies whether the target
1075     * should be added to or removed from the excluded list.
1076     *
1077     * <p>Excluding targets is a general mechanism for allowing transitions to run on
1078     * a view hierarchy while skipping target views that should not be part of
1079     * the transition. For example, you may want to avoid animating children
1080     * of a specific ListView or Spinner. Views can be excluded either by their
1081     * id, or by their instance reference, or by the Class of that view
1082     * (eg, {@link Spinner}).</p>
1083     *
1084     * @see #excludeChildren(int, boolean)
1085     * @see #excludeTarget(View, boolean)
1086     * @see #excludeTarget(Class, boolean)
1087     *
1088     * @param targetId The id of a target to ignore when running this transition.
1089     * @param exclude Whether to add the target to or remove the target from the
1090     * current list of excluded targets.
1091     * @return This transition object.
1092     */
1093    public Transition excludeTarget(int targetId, boolean exclude) {
1094        if (targetId >= 0) {
1095            mTargetIdExcludes = excludeObject(mTargetIdExcludes, targetId, exclude);
1096        }
1097        return this;
1098    }
1099
1100    /**
1101     * Whether to add the given transitionName to the list of target transitionNames to exclude
1102     * from this transition. The <code>exclude</code> parameter specifies whether the target
1103     * should be added to or removed from the excluded list.
1104     *
1105     * <p>Excluding targets is a general mechanism for allowing transitions to run on
1106     * a view hierarchy while skipping target views that should not be part of
1107     * the transition. For example, you may want to avoid animating children
1108     * of a specific ListView or Spinner. Views can be excluded by their
1109     * id, their instance reference, their transitionName, or by the Class of that view
1110     * (eg, {@link Spinner}).</p>
1111     *
1112     * @see #excludeTarget(View, boolean)
1113     * @see #excludeTarget(int, boolean)
1114     * @see #excludeTarget(Class, boolean)
1115     *
1116     * @param targetName The name of a target to ignore when running this transition.
1117     * @param exclude Whether to add the target to or remove the target from the
1118     * current list of excluded targets.
1119     * @return This transition object.
1120     */
1121    public Transition excludeTarget(String targetName, boolean exclude) {
1122        mTargetNameExcludes = excludeObject(mTargetNameExcludes, targetName, exclude);
1123        return this;
1124    }
1125
1126    /**
1127     * Whether to add the children of the given id to the list of targets to exclude
1128     * from this transition. The <code>exclude</code> parameter specifies whether
1129     * the children of the target should be added to or removed from the excluded list.
1130     * Excluding children in this way provides a simple mechanism for excluding all
1131     * children of specific targets, rather than individually excluding each
1132     * child individually.
1133     *
1134     * <p>Excluding targets is a general mechanism for allowing transitions to run on
1135     * a view hierarchy while skipping target views that should not be part of
1136     * the transition. For example, you may want to avoid animating children
1137     * of a specific ListView or Spinner. Views can be excluded either by their
1138     * id, or by their instance reference, or by the Class of that view
1139     * (eg, {@link Spinner}).</p>
1140     *
1141     * @see #excludeTarget(int, boolean)
1142     * @see #excludeChildren(View, boolean)
1143     * @see #excludeChildren(Class, boolean)
1144     *
1145     * @param targetId The id of a target whose children should be ignored when running
1146     * this transition.
1147     * @param exclude Whether to add the target to or remove the target from the
1148     * current list of excluded-child targets.
1149     * @return This transition object.
1150     */
1151    public Transition excludeChildren(int targetId, boolean exclude) {
1152        if (targetId >= 0) {
1153            mTargetIdChildExcludes = excludeObject(mTargetIdChildExcludes, targetId, exclude);
1154        }
1155        return this;
1156    }
1157
1158    /**
1159     * Whether to add the given target to the list of targets to exclude from this
1160     * transition. The <code>exclude</code> parameter specifies whether the target
1161     * should be added to or removed from the excluded list.
1162     *
1163     * <p>Excluding targets is a general mechanism for allowing transitions to run on
1164     * a view hierarchy while skipping target views that should not be part of
1165     * the transition. For example, you may want to avoid animating children
1166     * of a specific ListView or Spinner. Views can be excluded either by their
1167     * id, or by their instance reference, or by the Class of that view
1168     * (eg, {@link Spinner}).</p>
1169     *
1170     * @see #excludeChildren(View, boolean)
1171     * @see #excludeTarget(int, boolean)
1172     * @see #excludeTarget(Class, boolean)
1173     *
1174     * @param target The target to ignore when running this transition.
1175     * @param exclude Whether to add the target to or remove the target from the
1176     * current list of excluded targets.
1177     * @return This transition object.
1178     */
1179    public Transition excludeTarget(View target, boolean exclude) {
1180        mTargetExcludes = excludeObject(mTargetExcludes, target, exclude);
1181        return this;
1182    }
1183
1184    /**
1185     * Whether to add the children of given target to the list of target children
1186     * to exclude from this transition. The <code>exclude</code> parameter specifies
1187     * whether the target should be added to or removed from the excluded list.
1188     *
1189     * <p>Excluding targets is a general mechanism for allowing transitions to run on
1190     * a view hierarchy while skipping target views that should not be part of
1191     * the transition. For example, you may want to avoid animating children
1192     * of a specific ListView or Spinner. Views can be excluded either by their
1193     * id, or by their instance reference, or by the Class of that view
1194     * (eg, {@link Spinner}).</p>
1195     *
1196     * @see #excludeTarget(View, boolean)
1197     * @see #excludeChildren(int, boolean)
1198     * @see #excludeChildren(Class, boolean)
1199     *
1200     * @param target The target to ignore when running this transition.
1201     * @param exclude Whether to add the target to or remove the target from the
1202     * current list of excluded targets.
1203     * @return This transition object.
1204     */
1205    public Transition excludeChildren(View target, boolean exclude) {
1206        mTargetChildExcludes = excludeObject(mTargetChildExcludes, target, exclude);
1207        return this;
1208    }
1209
1210    /**
1211     * Utility method to manage the boilerplate code that is the same whether we
1212     * are excluding targets or their children.
1213     */
1214    private static <T> ArrayList<T> excludeObject(ArrayList<T> list, T target, boolean exclude) {
1215        if (target != null) {
1216            if (exclude) {
1217                list = ArrayListManager.add(list, target);
1218            } else {
1219                list = ArrayListManager.remove(list, target);
1220            }
1221        }
1222        return list;
1223    }
1224
1225    /**
1226     * Whether to add the given type to the list of types to exclude from this
1227     * transition. The <code>exclude</code> parameter specifies whether the target
1228     * type should be added to or removed from the excluded list.
1229     *
1230     * <p>Excluding targets is a general mechanism for allowing transitions to run on
1231     * a view hierarchy while skipping target views that should not be part of
1232     * the transition. For example, you may want to avoid animating children
1233     * of a specific ListView or Spinner. Views can be excluded either by their
1234     * id, or by their instance reference, or by the Class of that view
1235     * (eg, {@link Spinner}).</p>
1236     *
1237     * @see #excludeChildren(Class, boolean)
1238     * @see #excludeTarget(int, boolean)
1239     * @see #excludeTarget(View, boolean)
1240     *
1241     * @param type The type to ignore when running this transition.
1242     * @param exclude Whether to add the target type to or remove it from the
1243     * current list of excluded target types.
1244     * @return This transition object.
1245     */
1246    public Transition excludeTarget(Class type, boolean exclude) {
1247        mTargetTypeExcludes = excludeObject(mTargetTypeExcludes, type, exclude);
1248        return this;
1249    }
1250
1251    /**
1252     * Whether to add the given type to the list of types whose children should
1253     * be excluded from this transition. The <code>exclude</code> parameter
1254     * specifies whether the target type should be added to or removed from
1255     * the excluded list.
1256     *
1257     * <p>Excluding targets is a general mechanism for allowing transitions to run on
1258     * a view hierarchy while skipping target views that should not be part of
1259     * the transition. For example, you may want to avoid animating children
1260     * of a specific ListView or Spinner. Views can be excluded either by their
1261     * id, or by their instance reference, or by the Class of that view
1262     * (eg, {@link Spinner}).</p>
1263     *
1264     * @see #excludeTarget(Class, boolean)
1265     * @see #excludeChildren(int, boolean)
1266     * @see #excludeChildren(View, boolean)
1267     *
1268     * @param type The type to ignore when running this transition.
1269     * @param exclude Whether to add the target type to or remove it from the
1270     * current list of excluded target types.
1271     * @return This transition object.
1272     */
1273    public Transition excludeChildren(Class type, boolean exclude) {
1274        mTargetTypeChildExcludes = excludeObject(mTargetTypeChildExcludes, type, exclude);
1275        return this;
1276    }
1277
1278    /**
1279     * Sets the target view instances that this Transition is interested in
1280     * animating. By default, there are no targets, and a Transition will
1281     * listen for changes on every view in the hierarchy below the sceneRoot
1282     * of the Scene being transitioned into. Setting targets constrains
1283     * the Transition to only listen for, and act on, these views.
1284     * All other views will be ignored.
1285     *
1286     * <p>The target list is like the {@link #addTarget(int) targetId}
1287     * list except this list specifies the actual View instances, not the ids
1288     * of the views. This is an important distinction when scene changes involve
1289     * view hierarchies which have been inflated separately; different views may
1290     * share the same id but not actually be the same instance. If the transition
1291     * should treat those views as the same, then {@link #addTarget(int)} should be used
1292     * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve
1293     * changes all within the same view hierarchy, among views which do not
1294     * necessarily have ids set on them, then the target list of views may be more
1295     * convenient.</p>
1296     *
1297     * @see #addTarget(int)
1298     * @param target A View on which the Transition will act, must be non-null.
1299     * @return The Transition to which the target is added.
1300     * Returning the same object makes it easier to chain calls during
1301     * construction, such as
1302     * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code>
1303     */
1304    public Transition addTarget(View target) {
1305        mTargets.add(target);
1306        return this;
1307    }
1308
1309    /**
1310     * Removes the given target from the list of targets that this Transition
1311     * is interested in animating.
1312     *
1313     * @param target The target view, must be non-null.
1314     * @return Transition The Transition from which the target is removed.
1315     * Returning the same object makes it easier to chain calls during
1316     * construction, such as
1317     * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code>
1318     */
1319    public Transition removeTarget(View target) {
1320        if (target != null) {
1321            mTargets.remove(target);
1322        }
1323        return this;
1324    }
1325
1326    /**
1327     * Removes the given target from the list of targets that this Transition
1328     * is interested in animating.
1329     *
1330     * @param target The type of the target view, must be non-null.
1331     * @return Transition The Transition from which the target is removed.
1332     * Returning the same object makes it easier to chain calls during
1333     * construction, such as
1334     * <code>transitionSet.addTransitions(new Fade()).removeTarget(someType);</code>
1335     */
1336    public Transition removeTarget(Class target) {
1337        if (target != null) {
1338            mTargetTypes.remove(target);
1339        }
1340        return this;
1341    }
1342
1343    /**
1344     * Returns the list of target IDs that this transition limits itself to
1345     * tracking and animating. If the list is null or empty for
1346     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
1347     * {@link #getTargetTypes()} then this transition is
1348     * not limited to specific views, and will handle changes to any views
1349     * in the hierarchy of a scene change.
1350     *
1351     * @return the list of target IDs
1352     */
1353    public List<Integer> getTargetIds() {
1354        return mTargetIds;
1355    }
1356
1357    /**
1358     * Returns the list of target views that this transition limits itself to
1359     * tracking and animating. If the list is null or empty for
1360     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
1361     * {@link #getTargetTypes()} then this transition is
1362     * not limited to specific views, and will handle changes to any views
1363     * in the hierarchy of a scene change.
1364     *
1365     * @return the list of target views
1366     */
1367    public List<View> getTargets() {
1368        return mTargets;
1369    }
1370
1371    /**
1372     * Returns the list of target transitionNames that this transition limits itself to
1373     * tracking and animating. If the list is null or empty for
1374     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
1375     * {@link #getTargetTypes()} then this transition is
1376     * not limited to specific views, and will handle changes to any views
1377     * in the hierarchy of a scene change.
1378     *
1379     * @return the list of target transitionNames
1380     */
1381    public List<String> getTargetNames() {
1382        return mTargetNames;
1383    }
1384
1385    /**
1386     * To be removed before L release.
1387     * @hide
1388     */
1389    public List<String> getTargetViewNames() {
1390        return mTargetNames;
1391    }
1392
1393    /**
1394     * Returns the list of target transitionNames that this transition limits itself to
1395     * tracking and animating. If the list is null or empty for
1396     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
1397     * {@link #getTargetTypes()} then this transition is
1398     * not limited to specific views, and will handle changes to any views
1399     * in the hierarchy of a scene change.
1400     *
1401     * @return the list of target Types
1402     */
1403    public List<Class> getTargetTypes() {
1404        return mTargetTypes;
1405    }
1406
1407    /**
1408     * Recursive method that captures values for the given view and the
1409     * hierarchy underneath it.
1410     * @param sceneRoot The root of the view hierarchy being captured
1411     * @param start true if this capture is happening before the scene change,
1412     * false otherwise
1413     */
1414    void captureValues(ViewGroup sceneRoot, boolean start) {
1415        clearValues(start);
1416        if ((mTargetIds.size() > 0 || mTargets.size() > 0)
1417                && (mTargetNames == null || mTargetNames.isEmpty())
1418                && (mTargetTypes == null || mTargetTypes.isEmpty())) {
1419            for (int i = 0; i < mTargetIds.size(); ++i) {
1420                int id = mTargetIds.get(i);
1421                View view = sceneRoot.findViewById(id);
1422                if (view != null) {
1423                    TransitionValues values = new TransitionValues();
1424                    values.view = view;
1425                    if (start) {
1426                        captureStartValues(values);
1427                    } else {
1428                        captureEndValues(values);
1429                    }
1430                    capturePropagationValues(values);
1431                    if (start) {
1432                        addViewValues(mStartValues, view, values);
1433                    } else {
1434                        addViewValues(mEndValues, view, values);
1435                    }
1436                }
1437            }
1438            for (int i = 0; i < mTargets.size(); ++i) {
1439                View view = mTargets.get(i);
1440                TransitionValues values = new TransitionValues();
1441                values.view = view;
1442                if (start) {
1443                    captureStartValues(values);
1444                } else {
1445                    captureEndValues(values);
1446                }
1447                capturePropagationValues(values);
1448                if (start) {
1449                    mStartValues.viewValues.put(view, values);
1450                } else {
1451                    mEndValues.viewValues.put(view, values);
1452                }
1453            }
1454        } else {
1455            captureHierarchy(sceneRoot, start);
1456        }
1457        if (!start && mNameOverrides != null) {
1458            int numOverrides = mNameOverrides.size();
1459            ArrayList<View> overriddenViews = new ArrayList<View>(numOverrides);
1460            for (int i = 0; i < numOverrides; i++) {
1461                String fromName = mNameOverrides.keyAt(i);
1462                overriddenViews.add(mStartValues.nameValues.remove(fromName));
1463            }
1464            for (int i = 0; i < numOverrides; i++) {
1465                View view = overriddenViews.get(i);
1466                if (view != null) {
1467                    String toName = mNameOverrides.valueAt(i);
1468                    mStartValues.nameValues.put(toName, view);
1469                }
1470            }
1471        }
1472    }
1473
1474    static void addViewValues(TransitionValuesMaps transitionValuesMaps,
1475            View view, TransitionValues transitionValues) {
1476        transitionValuesMaps.viewValues.put(view, transitionValues);
1477        int id = view.getId();
1478        if (id >= 0) {
1479            if (transitionValuesMaps.idValues.indexOfKey(id) >= 0) {
1480                // Duplicate IDs cannot match by ID.
1481                transitionValuesMaps.idValues.put(id, null);
1482            } else {
1483                transitionValuesMaps.idValues.put(id, view);
1484            }
1485        }
1486        String name = view.getTransitionName();
1487        if (name != null) {
1488            if (transitionValuesMaps.nameValues.containsKey(name)) {
1489                // Duplicate transitionNames: cannot match by transitionName.
1490                transitionValuesMaps.nameValues.put(name, null);
1491            } else {
1492                transitionValuesMaps.nameValues.put(name, view);
1493            }
1494        }
1495        if (view.getParent() instanceof ListView) {
1496            ListView listview = (ListView) view.getParent();
1497            if (listview.getAdapter().hasStableIds()) {
1498                int position = listview.getPositionForView(view);
1499                long itemId = listview.getItemIdAtPosition(position);
1500                if (transitionValuesMaps.itemIdValues.indexOfKey(itemId) >= 0) {
1501                    // Duplicate item IDs: cannot match by item ID.
1502                    View alreadyMatched = transitionValuesMaps.itemIdValues.get(itemId);
1503                    if (alreadyMatched != null) {
1504                        alreadyMatched.setHasTransientState(false);
1505                        transitionValuesMaps.itemIdValues.put(itemId, null);
1506                    }
1507                } else {
1508                    view.setHasTransientState(true);
1509                    transitionValuesMaps.itemIdValues.put(itemId, view);
1510                }
1511            }
1512        }
1513    }
1514
1515    /**
1516     * Clear valuesMaps for specified start/end state
1517     *
1518     * @param start true if the start values should be cleared, false otherwise
1519     */
1520    void clearValues(boolean start) {
1521        if (start) {
1522            mStartValues.viewValues.clear();
1523            mStartValues.idValues.clear();
1524            mStartValues.itemIdValues.clear();
1525            mStartValues.nameValues.clear();
1526        } else {
1527            mEndValues.viewValues.clear();
1528            mEndValues.idValues.clear();
1529            mEndValues.itemIdValues.clear();
1530            mEndValues.nameValues.clear();
1531        }
1532    }
1533
1534    /**
1535     * Recursive method which captures values for an entire view hierarchy,
1536     * starting at some root view. Transitions without targetIDs will use this
1537     * method to capture values for all possible views.
1538     *
1539     * @param view The view for which to capture values. Children of this View
1540     * will also be captured, recursively down to the leaf nodes.
1541     * @param start true if values are being captured in the start scene, false
1542     * otherwise.
1543     */
1544    private void captureHierarchy(View view, boolean start) {
1545        if (view == null) {
1546            return;
1547        }
1548        int id = view.getId();
1549        if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) {
1550            return;
1551        }
1552        if (mTargetExcludes != null && mTargetExcludes.contains(view)) {
1553            return;
1554        }
1555        if (mTargetTypeExcludes != null && view != null) {
1556            int numTypes = mTargetTypeExcludes.size();
1557            for (int i = 0; i < numTypes; ++i) {
1558                if (mTargetTypeExcludes.get(i).isInstance(view)) {
1559                    return;
1560                }
1561            }
1562        }
1563        if (view.getParent() instanceof ViewGroup) {
1564            TransitionValues values = new TransitionValues();
1565            values.view = view;
1566            if (start) {
1567                captureStartValues(values);
1568            } else {
1569                captureEndValues(values);
1570            }
1571            capturePropagationValues(values);
1572            if (start) {
1573                addViewValues(mStartValues, view, values);
1574            } else {
1575                addViewValues(mEndValues, view, values);
1576            }
1577        }
1578        if (view instanceof ViewGroup) {
1579            // Don't traverse child hierarchy if there are any child-excludes on this view
1580            if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) {
1581                return;
1582            }
1583            if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) {
1584                return;
1585            }
1586            if (mTargetTypeChildExcludes != null) {
1587                int numTypes = mTargetTypeChildExcludes.size();
1588                for (int i = 0; i < numTypes; ++i) {
1589                    if (mTargetTypeChildExcludes.get(i).isInstance(view)) {
1590                        return;
1591                    }
1592                }
1593            }
1594            ViewGroup parent = (ViewGroup) view;
1595            for (int i = 0; i < parent.getChildCount(); ++i) {
1596                captureHierarchy(parent.getChildAt(i), start);
1597            }
1598        }
1599    }
1600
1601    /**
1602     * This method can be called by transitions to get the TransitionValues for
1603     * any particular view during the transition-playing process. This might be
1604     * necessary, for example, to query the before/after state of related views
1605     * for a given transition.
1606     */
1607    public TransitionValues getTransitionValues(View view, boolean start) {
1608        if (mParent != null) {
1609            return mParent.getTransitionValues(view, start);
1610        }
1611        TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
1612        return valuesMaps.viewValues.get(view);
1613    }
1614
1615    /**
1616     * Pauses this transition, sending out calls to {@link
1617     * TransitionListener#onTransitionPause(Transition)} to all listeners
1618     * and pausing all running animators started by this transition.
1619     *
1620     * @hide
1621     */
1622    public void pause(View sceneRoot) {
1623        if (!mEnded) {
1624            ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1625            int numOldAnims = runningAnimators.size();
1626            if (sceneRoot != null) {
1627                WindowId windowId = sceneRoot.getWindowId();
1628                for (int i = numOldAnims - 1; i >= 0; i--) {
1629                    AnimationInfo info = runningAnimators.valueAt(i);
1630                    if (info.view != null && windowId.equals(info.windowId)) {
1631                        Animator anim = runningAnimators.keyAt(i);
1632                        anim.pause();
1633                    }
1634                }
1635            }
1636            if (mListeners != null && mListeners.size() > 0) {
1637                ArrayList<TransitionListener> tmpListeners =
1638                        (ArrayList<TransitionListener>) mListeners.clone();
1639                int numListeners = tmpListeners.size();
1640                for (int i = 0; i < numListeners; ++i) {
1641                    tmpListeners.get(i).onTransitionPause(this);
1642                }
1643            }
1644            mPaused = true;
1645        }
1646    }
1647
1648    /**
1649     * Resumes this transition, sending out calls to {@link
1650     * TransitionListener#onTransitionPause(Transition)} to all listeners
1651     * and pausing all running animators started by this transition.
1652     *
1653     * @hide
1654     */
1655    public void resume(View sceneRoot) {
1656        if (mPaused) {
1657            if (!mEnded) {
1658                ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1659                int numOldAnims = runningAnimators.size();
1660                WindowId windowId = sceneRoot.getWindowId();
1661                for (int i = numOldAnims - 1; i >= 0; i--) {
1662                    AnimationInfo info = runningAnimators.valueAt(i);
1663                    if (info.view != null && windowId.equals(info.windowId)) {
1664                        Animator anim = runningAnimators.keyAt(i);
1665                        anim.resume();
1666                    }
1667                }
1668                if (mListeners != null && mListeners.size() > 0) {
1669                    ArrayList<TransitionListener> tmpListeners =
1670                            (ArrayList<TransitionListener>) mListeners.clone();
1671                    int numListeners = tmpListeners.size();
1672                    for (int i = 0; i < numListeners; ++i) {
1673                        tmpListeners.get(i).onTransitionResume(this);
1674                    }
1675                }
1676            }
1677            mPaused = false;
1678        }
1679    }
1680
1681    /**
1682     * Called by TransitionManager to play the transition. This calls
1683     * createAnimators() to set things up and create all of the animations and then
1684     * runAnimations() to actually start the animations.
1685     */
1686    void playTransition(ViewGroup sceneRoot) {
1687        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1688        int numOldAnims = runningAnimators.size();
1689        WindowId windowId = sceneRoot.getWindowId();
1690        for (int i = numOldAnims - 1; i >= 0; i--) {
1691            Animator anim = runningAnimators.keyAt(i);
1692            if (anim != null) {
1693                AnimationInfo oldInfo = runningAnimators.get(anim);
1694                if (oldInfo != null && oldInfo.view != null && oldInfo.windowId == windowId) {
1695                    TransitionValues oldValues = oldInfo.values;
1696                    View oldView = oldInfo.view;
1697                    TransitionValues newValues = mEndValues.viewValues.get(oldView);
1698                    boolean cancel = oldInfo.transition.areValuesChanged(oldValues, newValues);
1699                    if (cancel) {
1700                        if (anim.isRunning() || anim.isStarted()) {
1701                            if (DBG) {
1702                                Log.d(LOG_TAG, "Canceling anim " + anim);
1703                            }
1704                            anim.cancel();
1705                        } else {
1706                            if (DBG) {
1707                                Log.d(LOG_TAG, "removing anim from info list: " + anim);
1708                            }
1709                            runningAnimators.remove(anim);
1710                        }
1711                    }
1712                }
1713            }
1714        }
1715
1716        createAnimators(sceneRoot, mStartValues, mEndValues);
1717        runAnimators();
1718    }
1719
1720    boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) {
1721        boolean valuesChanged = false;
1722        // if oldValues null, then transition didn't care to stash values,
1723        // and won't get canceled
1724        if (oldValues != null && newValues != null) {
1725            for (String key : oldValues.values.keySet()) {
1726                Object oldValue = oldValues.values.get(key);
1727                Object newValue = newValues.values.get(key);
1728                if (oldValue != null && newValue != null &&
1729                        !oldValue.equals(newValue)) {
1730                    valuesChanged = true;
1731                    if (DBG) {
1732                        Log.d(LOG_TAG, "Transition.playTransition: " +
1733                                "oldValue != newValue for " + key +
1734                                ": old, new = " + oldValue + ", " + newValue);
1735                    }
1736                    break;
1737                }
1738            }
1739        }
1740        return valuesChanged;
1741    }
1742
1743    /**
1744     * This is a utility method used by subclasses to handle standard parts of
1745     * setting up and running an Animator: it sets the {@link #getDuration()
1746     * duration} and the {@link #getStartDelay() startDelay}, starts the
1747     * animation, and, when the animator ends, calls {@link #end()}.
1748     *
1749     * @param animator The Animator to be run during this transition.
1750     *
1751     * @hide
1752     */
1753    protected void animate(Animator animator) {
1754        // TODO: maybe pass auto-end as a boolean parameter?
1755        if (animator == null) {
1756            end();
1757        } else {
1758            if (getDuration() >= 0) {
1759                animator.setDuration(getDuration());
1760            }
1761            if (getStartDelay() >= 0) {
1762                animator.setStartDelay(getStartDelay() + animator.getStartDelay());
1763            }
1764            if (getInterpolator() != null) {
1765                animator.setInterpolator(getInterpolator());
1766            }
1767            animator.addListener(new AnimatorListenerAdapter() {
1768                @Override
1769                public void onAnimationEnd(Animator animation) {
1770                    end();
1771                    animation.removeListener(this);
1772                }
1773            });
1774            animator.start();
1775        }
1776    }
1777
1778    /**
1779     * This method is called automatically by the transition and
1780     * TransitionSet classes prior to a Transition subclass starting;
1781     * subclasses should not need to call it directly.
1782     *
1783     * @hide
1784     */
1785    protected void start() {
1786        if (mNumInstances == 0) {
1787            if (mListeners != null && mListeners.size() > 0) {
1788                ArrayList<TransitionListener> tmpListeners =
1789                        (ArrayList<TransitionListener>) mListeners.clone();
1790                int numListeners = tmpListeners.size();
1791                for (int i = 0; i < numListeners; ++i) {
1792                    tmpListeners.get(i).onTransitionStart(this);
1793                }
1794            }
1795            mEnded = false;
1796        }
1797        mNumInstances++;
1798    }
1799
1800    /**
1801     * This method is called automatically by the Transition and
1802     * TransitionSet classes when a transition finishes, either because
1803     * a transition did nothing (returned a null Animator from
1804     * {@link Transition#createAnimator(ViewGroup, TransitionValues,
1805     * TransitionValues)}) or because the transition returned a valid
1806     * Animator and end() was called in the onAnimationEnd()
1807     * callback of the AnimatorListener.
1808     *
1809     * @hide
1810     */
1811    protected void end() {
1812        --mNumInstances;
1813        if (mNumInstances == 0) {
1814            if (mListeners != null && mListeners.size() > 0) {
1815                ArrayList<TransitionListener> tmpListeners =
1816                        (ArrayList<TransitionListener>) mListeners.clone();
1817                int numListeners = tmpListeners.size();
1818                for (int i = 0; i < numListeners; ++i) {
1819                    tmpListeners.get(i).onTransitionEnd(this);
1820                }
1821            }
1822            for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) {
1823                View view = mStartValues.itemIdValues.valueAt(i);
1824                if (view != null) {
1825                    view.setHasTransientState(false);
1826                }
1827            }
1828            for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) {
1829                View view = mEndValues.itemIdValues.valueAt(i);
1830                if (view != null) {
1831                    view.setHasTransientState(false);
1832                }
1833            }
1834            mEnded = true;
1835        }
1836    }
1837
1838    /**
1839     * This method cancels a transition that is currently running.
1840     *
1841     * @hide
1842     */
1843    protected void cancel() {
1844        int numAnimators = mCurrentAnimators.size();
1845        for (int i = numAnimators - 1; i >= 0; i--) {
1846            Animator animator = mCurrentAnimators.get(i);
1847            animator.cancel();
1848        }
1849        if (mListeners != null && mListeners.size() > 0) {
1850            ArrayList<TransitionListener> tmpListeners =
1851                    (ArrayList<TransitionListener>) mListeners.clone();
1852            int numListeners = tmpListeners.size();
1853            for (int i = 0; i < numListeners; ++i) {
1854                tmpListeners.get(i).onTransitionCancel(this);
1855            }
1856        }
1857    }
1858
1859    /**
1860     * Adds a listener to the set of listeners that are sent events through the
1861     * life of an animation, such as start, repeat, and end.
1862     *
1863     * @param listener the listener to be added to the current set of listeners
1864     * for this animation.
1865     * @return This transition object.
1866     */
1867    public Transition addListener(TransitionListener listener) {
1868        if (mListeners == null) {
1869            mListeners = new ArrayList<TransitionListener>();
1870        }
1871        mListeners.add(listener);
1872        return this;
1873    }
1874
1875    /**
1876     * Removes a listener from the set listening to this animation.
1877     *
1878     * @param listener the listener to be removed from the current set of
1879     * listeners for this transition.
1880     * @return This transition object.
1881     */
1882    public Transition removeListener(TransitionListener listener) {
1883        if (mListeners == null) {
1884            return this;
1885        }
1886        mListeners.remove(listener);
1887        if (mListeners.size() == 0) {
1888            mListeners = null;
1889        }
1890        return this;
1891    }
1892
1893    /**
1894     * Sets the callback to use to find the epicenter of a Transition. A null value indicates
1895     * that there is no epicenter in the Transition and onGetEpicenter() will return null.
1896     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
1897     * the direction of travel. This is called the epicenter of the Transition and is
1898     * typically centered on a touched View. The
1899     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
1900     * dynamically retrieve the epicenter during a Transition.
1901     * @param epicenterCallback The callback to use to find the epicenter of the Transition.
1902     */
1903    public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
1904        mEpicenterCallback = epicenterCallback;
1905    }
1906
1907    /**
1908     * Returns the callback used to find the epicenter of the Transition.
1909     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
1910     * the direction of travel. This is called the epicenter of the Transition and is
1911     * typically centered on a touched View. The
1912     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
1913     * dynamically retrieve the epicenter during a Transition.
1914     * @return the callback used to find the epicenter of the Transition.
1915     */
1916    public EpicenterCallback getEpicenterCallback() {
1917        return mEpicenterCallback;
1918    }
1919
1920    /**
1921     * Returns the epicenter as specified by the
1922     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
1923     * @return the epicenter as specified by the
1924     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
1925     * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
1926     */
1927    public Rect getEpicenter() {
1928        if (mEpicenterCallback == null) {
1929            return null;
1930        }
1931        return mEpicenterCallback.onGetEpicenter(this);
1932    }
1933
1934    /**
1935     * Sets the algorithm used to calculate two-dimensional interpolation.
1936     * <p>
1937     *     Transitions such as {@link android.transition.ChangeBounds} move Views, typically
1938     *     in a straight path between the start and end positions. Applications that desire to
1939     *     have these motions move in a curve can change how Views interpolate in two dimensions
1940     *     by extending PathMotion and implementing
1941     *     {@link android.transition.PathMotion#getPath(float, float, float, float)}.
1942     * </p>
1943     * @param pathMotion Algorithm object to use for determining how to interpolate in two
1944     *                   dimensions. If null, a straight-path algorithm will be used.
1945     */
1946    public void setPathMotion(PathMotion pathMotion) {
1947        if (pathMotion == null) {
1948            mPathMotion = STRAIGHT_PATH_MOTION;
1949        } else {
1950            mPathMotion = pathMotion;
1951        }
1952    }
1953
1954    /**
1955     * Returns the algorithm object used to interpolate along two dimensions. This is typically
1956     * used to determine the View motion between two points.
1957     *
1958     * @return The algorithm object used to interpolate along two dimensions.
1959     */
1960    public PathMotion getPathMotion() {
1961        return mPathMotion;
1962    }
1963
1964    /**
1965     * Sets the method for determining Animator start delays.
1966     * When a Transition affects several Views like {@link android.transition.Explode} or
1967     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
1968     * such that the Animator start delay depends on position of the View. The
1969     * TransitionPropagation specifies how the start delays are calculated.
1970     * @param transitionPropagation The class used to determine the start delay of
1971     *                              Animators created by this Transition. A null value
1972     *                              indicates that no delay should be used.
1973     */
1974    public void setPropagation(TransitionPropagation transitionPropagation) {
1975        mPropagation = transitionPropagation;
1976    }
1977
1978    /**
1979     * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator start
1980     * delays.
1981     * When a Transition affects several Views like {@link android.transition.Explode} or
1982     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
1983     * such that the Animator start delay depends on position of the View. The
1984     * TransitionPropagation specifies how the start delays are calculated.
1985     * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start
1986     * delays. This is null by default.
1987     */
1988    public TransitionPropagation getPropagation() {
1989        return mPropagation;
1990    }
1991
1992    /**
1993     * Captures TransitionPropagation values for the given view and the
1994     * hierarchy underneath it.
1995     */
1996    void capturePropagationValues(TransitionValues transitionValues) {
1997        if (mPropagation != null && !transitionValues.values.isEmpty()) {
1998            String[] propertyNames = mPropagation.getPropagationProperties();
1999            if (propertyNames == null) {
2000                return;
2001            }
2002            boolean containsAll = true;
2003            for (int i = 0; i < propertyNames.length; i++) {
2004                if (!transitionValues.values.containsKey(propertyNames[i])) {
2005                    containsAll = false;
2006                    break;
2007                }
2008            }
2009            if (!containsAll) {
2010                mPropagation.captureValues(transitionValues);
2011            }
2012        }
2013    }
2014
2015    Transition setSceneRoot(ViewGroup sceneRoot) {
2016        mSceneRoot = sceneRoot;
2017        return this;
2018    }
2019
2020    void setCanRemoveViews(boolean canRemoveViews) {
2021        mCanRemoveViews = canRemoveViews;
2022    }
2023
2024    public boolean canRemoveViews() {
2025        return mCanRemoveViews;
2026    }
2027
2028    /**
2029     * Sets the shared element names -- a mapping from a name at the start state to
2030     * a different name at the end state.
2031     * @hide
2032     */
2033    public void setNameOverrides(ArrayMap<String, String> overrides) {
2034        mNameOverrides = overrides;
2035    }
2036
2037    /** @hide */
2038    public ArrayMap<String, String> getNameOverrides() {
2039        return mNameOverrides;
2040    }
2041
2042    /** @hide */
2043    public void forceVisibility(int visibility, boolean isStartValue) {}
2044
2045    @Override
2046    public String toString() {
2047        return toString("");
2048    }
2049
2050    @Override
2051    public Transition clone() {
2052        Transition clone = null;
2053        try {
2054            clone = (Transition) super.clone();
2055            clone.mAnimators = new ArrayList<Animator>();
2056            clone.mStartValues = new TransitionValuesMaps();
2057            clone.mEndValues = new TransitionValuesMaps();
2058        } catch (CloneNotSupportedException e) {}
2059
2060        return clone;
2061    }
2062
2063    /**
2064     * Returns the name of this Transition. This name is used internally to distinguish
2065     * between different transitions to determine when interrupting transitions overlap.
2066     * For example, a ChangeBounds running on the same target view as another ChangeBounds
2067     * should determine whether the old transition is animating to different end values
2068     * and should be canceled in favor of the new transition.
2069     *
2070     * <p>By default, a Transition's name is simply the value of {@link Class#getName()},
2071     * but subclasses are free to override and return something different.</p>
2072     *
2073     * @return The name of this transition.
2074     */
2075    public String getName() {
2076        return mName;
2077    }
2078
2079    String toString(String indent) {
2080        String result = indent + getClass().getSimpleName() + "@" +
2081                Integer.toHexString(hashCode()) + ": ";
2082        if (mDuration != -1) {
2083            result += "dur(" + mDuration + ") ";
2084        }
2085        if (mStartDelay != -1) {
2086            result += "dly(" + mStartDelay + ") ";
2087        }
2088        if (mInterpolator != null) {
2089            result += "interp(" + mInterpolator + ") ";
2090        }
2091        if (mTargetIds.size() > 0 || mTargets.size() > 0) {
2092            result += "tgts(";
2093            if (mTargetIds.size() > 0) {
2094                for (int i = 0; i < mTargetIds.size(); ++i) {
2095                    if (i > 0) {
2096                        result += ", ";
2097                    }
2098                    result += mTargetIds.get(i);
2099                }
2100            }
2101            if (mTargets.size() > 0) {
2102                for (int i = 0; i < mTargets.size(); ++i) {
2103                    if (i > 0) {
2104                        result += ", ";
2105                    }
2106                    result += mTargets.get(i);
2107                }
2108            }
2109            result += ")";
2110        }
2111        return result;
2112    }
2113
2114    /**
2115     * A transition listener receives notifications from a transition.
2116     * Notifications indicate transition lifecycle events.
2117     */
2118    public static interface TransitionListener {
2119        /**
2120         * Notification about the start of the transition.
2121         *
2122         * @param transition The started transition.
2123         */
2124        void onTransitionStart(Transition transition);
2125
2126        /**
2127         * Notification about the end of the transition. Canceled transitions
2128         * will always notify listeners of both the cancellation and end
2129         * events. That is, {@link #onTransitionEnd(Transition)} is always called,
2130         * regardless of whether the transition was canceled or played
2131         * through to completion.
2132         *
2133         * @param transition The transition which reached its end.
2134         */
2135        void onTransitionEnd(Transition transition);
2136
2137        /**
2138         * Notification about the cancellation of the transition.
2139         * Note that cancel may be called by a parent {@link TransitionSet} on
2140         * a child transition which has not yet started. This allows the child
2141         * transition to restore state on target objects which was set at
2142         * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
2143         * createAnimator()} time.
2144         *
2145         * @param transition The transition which was canceled.
2146         */
2147        void onTransitionCancel(Transition transition);
2148
2149        /**
2150         * Notification when a transition is paused.
2151         * Note that createAnimator() may be called by a parent {@link TransitionSet} on
2152         * a child transition which has not yet started. This allows the child
2153         * transition to restore state on target objects which was set at
2154         * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
2155         * createAnimator()} time.
2156         *
2157         * @param transition The transition which was paused.
2158         */
2159        void onTransitionPause(Transition transition);
2160
2161        /**
2162         * Notification when a transition is resumed.
2163         * Note that resume() may be called by a parent {@link TransitionSet} on
2164         * a child transition which has not yet started. This allows the child
2165         * transition to restore state which may have changed in an earlier call
2166         * to {@link #onTransitionPause(Transition)}.
2167         *
2168         * @param transition The transition which was resumed.
2169         */
2170        void onTransitionResume(Transition transition);
2171    }
2172
2173    /**
2174     * Utility adapter class to avoid having to override all three methods
2175     * whenever someone just wants to listen for a single event.
2176     *
2177     * @hide
2178     * */
2179    public static class TransitionListenerAdapter implements TransitionListener {
2180        @Override
2181        public void onTransitionStart(Transition transition) {
2182        }
2183
2184        @Override
2185        public void onTransitionEnd(Transition transition) {
2186        }
2187
2188        @Override
2189        public void onTransitionCancel(Transition transition) {
2190        }
2191
2192        @Override
2193        public void onTransitionPause(Transition transition) {
2194        }
2195
2196        @Override
2197        public void onTransitionResume(Transition transition) {
2198        }
2199    }
2200
2201    /**
2202     * Holds information about each animator used when a new transition starts
2203     * while other transitions are still running to determine whether a running
2204     * animation should be canceled or a new animation noop'd. The structure holds
2205     * information about the state that an animation is going to, to be compared to
2206     * end state of a new animation.
2207     * @hide
2208     */
2209    public static class AnimationInfo {
2210        public View view;
2211        String name;
2212        TransitionValues values;
2213        WindowId windowId;
2214        Transition transition;
2215
2216        AnimationInfo(View view, String name, Transition transition,
2217                WindowId windowId, TransitionValues values) {
2218            this.view = view;
2219            this.name = name;
2220            this.values = values;
2221            this.windowId = windowId;
2222            this.transition = transition;
2223        }
2224    }
2225
2226    /**
2227     * Utility class for managing typed ArrayLists efficiently. In particular, this
2228     * can be useful for lists that we don't expect to be used often (eg, the exclude
2229     * lists), so we'd like to keep them nulled out by default. This causes the code to
2230     * become tedious, with constant null checks, code to allocate when necessary,
2231     * and code to null out the reference when the list is empty. This class encapsulates
2232     * all of that functionality into simple add()/remove() methods which perform the
2233     * necessary checks, allocation/null-out as appropriate, and return the
2234     * resulting list.
2235     */
2236    private static class ArrayListManager {
2237
2238        /**
2239         * Add the specified item to the list, returning the resulting list.
2240         * The returned list can either the be same list passed in or, if that
2241         * list was null, the new list that was created.
2242         *
2243         * Note that the list holds unique items; if the item already exists in the
2244         * list, the list is not modified.
2245         */
2246        static <T> ArrayList<T> add(ArrayList<T> list, T item) {
2247            if (list == null) {
2248                list = new ArrayList<T>();
2249            }
2250            if (!list.contains(item)) {
2251                list.add(item);
2252            }
2253            return list;
2254        }
2255
2256        /**
2257         * Remove the specified item from the list, returning the resulting list.
2258         * The returned list can either the be same list passed in or, if that
2259         * list becomes empty as a result of the remove(), the new list was created.
2260         */
2261        static <T> ArrayList<T> remove(ArrayList<T> list, T item) {
2262            if (list != null) {
2263                list.remove(item);
2264                if (list.isEmpty()) {
2265                    list = null;
2266                }
2267            }
2268            return list;
2269        }
2270    }
2271
2272    /**
2273     * Class to get the epicenter of Transition. Use
2274     * {@link #setEpicenterCallback(android.transition.Transition.EpicenterCallback)} to
2275     * set the callback used to calculate the epicenter of the Transition. Override
2276     * {@link #getEpicenter()} to return the rectangular region in screen coordinates of
2277     * the epicenter of the transition.
2278     * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
2279     */
2280    public static abstract class EpicenterCallback {
2281
2282        /**
2283         * Implementers must override to return the epicenter of the Transition in screen
2284         * coordinates. Transitions like {@link android.transition.Explode} depend upon
2285         * an epicenter for the Transition. In Explode, Views move toward or away from the
2286         * center of the epicenter Rect along the vector between the epicenter and the center
2287         * of the View appearing and disappearing. Some Transitions, such as
2288         * {@link android.transition.Fade} pay no attention to the epicenter.
2289         *
2290         * @param transition The transition for which the epicenter applies.
2291         * @return The Rect region of the epicenter of <code>transition</code> or null if
2292         * there is no epicenter.
2293         */
2294        public abstract Rect onGetEpicenter(Transition transition);
2295    }
2296}
2297