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