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