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