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