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