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