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