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