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