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