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