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
17package androidx.transition;
18
19import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20
21import android.animation.Animator;
22import android.animation.AnimatorListenerAdapter;
23import android.animation.TimeInterpolator;
24import android.content.Context;
25import android.content.res.TypedArray;
26import android.content.res.XmlResourceParser;
27import android.graphics.Path;
28import android.graphics.Rect;
29import android.util.AttributeSet;
30import android.util.Log;
31import android.util.SparseArray;
32import android.util.SparseIntArray;
33import android.view.InflateException;
34import android.view.SurfaceView;
35import android.view.TextureView;
36import android.view.View;
37import android.view.ViewGroup;
38import android.view.animation.AnimationUtils;
39import android.widget.ListView;
40import android.widget.Spinner;
41
42import androidx.annotation.IdRes;
43import androidx.annotation.IntDef;
44import androidx.annotation.NonNull;
45import androidx.annotation.Nullable;
46import androidx.annotation.RestrictTo;
47import androidx.collection.ArrayMap;
48import androidx.collection.LongSparseArray;
49import androidx.core.content.res.TypedArrayUtils;
50import androidx.core.view.ViewCompat;
51
52import java.lang.annotation.Retention;
53import java.lang.annotation.RetentionPolicy;
54import java.util.ArrayList;
55import java.util.List;
56import 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 */
119public 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