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