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