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