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