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.util.ArrayMap;
     23 import android.util.Log;
     24 import android.util.LongSparseArray;
     25 import android.util.SparseArray;
     26 import android.view.SurfaceView;
     27 import android.view.TextureView;
     28 import android.view.View;
     29 import android.view.ViewGroup;
     30 import android.view.ViewOverlay;
     31 import android.widget.ListView;
     32 import android.widget.Spinner;
     33 
     34 import java.util.ArrayList;
     35 import java.util.List;
     36 
     37 /**
     38  * A Transition holds information about animations that will be run on its
     39  * targets during a scene change. Subclasses of this abstract class may
     40  * choreograph several child transitions ({@link TransitionSet} or they may
     41  * perform custom animations themselves. Any Transition has two main jobs:
     42  * (1) capture property values, and (2) play animations based on changes to
     43  * captured property values. A custom transition knows what property values
     44  * on View objects are of interest to it, and also knows how to animate
     45  * changes to those values. For example, the {@link Fade} transition tracks
     46  * changes to visibility-related properties and is able to construct and run
     47  * animations that fade items in or out based on changes to those properties.
     48  *
     49  * <p>Note: Transitions may not work correctly with either {@link SurfaceView}
     50  * or {@link TextureView}, due to the way that these views are displayed
     51  * on the screen. For SurfaceView, the problem is that the view is updated from
     52  * a non-UI thread, so changes to the view due to transitions (such as moving
     53  * and resizing the view) may be out of sync with the display inside those bounds.
     54  * TextureView is more compatible with transitions in general, but some
     55  * specific transitions (such as {@link Fade}) may not be compatible
     56  * with TextureView because they rely on {@link ViewOverlay} functionality,
     57  * which does not currently work with TextureView.</p>
     58  *
     59  * <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
     60  * directory. Transition resources consist of a tag name for one of the Transition
     61  * subclasses along with attributes to define some of the attributes of that transition.
     62  * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:</p>
     63  *
     64  * {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds}
     65  *
     66  * <p>Note that attributes for the transition are not required, just as they are
     67  * optional when declared in code; Transitions created from XML resources will use
     68  * the same defaults as their code-created equivalents. Here is a slightly more
     69  * elaborate example which declares a {@link TransitionSet} transition with
     70  * {@link ChangeBounds} and {@link Fade} child transitions:</p>
     71  *
     72  * {@sample
     73  * development/samples/ApiDemos/res/transition/changebounds_fadeout_sequential.xml TransitionSet}
     74  *
     75  * <p>In this example, the transitionOrdering attribute is used on the TransitionSet
     76  * object to change from the default {@link TransitionSet#ORDERING_TOGETHER} behavior
     77  * to be {@link TransitionSet#ORDERING_SEQUENTIAL} instead. Also, the {@link Fade}
     78  * transition uses a fadingMode of {@link Fade#OUT} instead of the default
     79  * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which
     80  * takes a set of {@link android.R.styleable#TransitionTarget target} tags, each
     81  * of which lists a specific <code>targetId</code> which this transition acts upon.
     82  * Use of targets is optional, but can be used to either limit the time spent checking
     83  * attributes on unchanging views, or limiting the types of animations run on specific views.
     84  * In this case, we know that only the <code>grayscaleContainer</code> will be
     85  * disappearing, so we choose to limit the {@link Fade} transition to only that view.</p>
     86  *
     87  * Further information on XML resource descriptions for transitions can be found for
     88  * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
     89  * {@link android.R.styleable#TransitionTarget}, and {@link android.R.styleable#Fade}.
     90  *
     91  */
     92 public abstract class Transition implements Cloneable {
     93 
     94     private static final String LOG_TAG = "Transition";
     95     static final boolean DBG = false;
     96 
     97     private String mName = getClass().getName();
     98 
     99     long mStartDelay = -1;
    100     long mDuration = -1;
    101     TimeInterpolator mInterpolator = null;
    102     ArrayList<Integer> mTargetIds = new ArrayList<Integer>();
    103     ArrayList<View> mTargets = new ArrayList<View>();
    104     ArrayList<Integer> mTargetIdExcludes = null;
    105     ArrayList<View> mTargetExcludes = null;
    106     ArrayList<Class> mTargetTypeExcludes = null;
    107     ArrayList<Integer> mTargetIdChildExcludes = null;
    108     ArrayList<View> mTargetChildExcludes = null;
    109     ArrayList<Class> mTargetTypeChildExcludes = null;
    110     private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
    111     private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
    112     TransitionSet mParent = null;
    113 
    114     // Per-animator information used for later canceling when future transitions overlap
    115     private static ThreadLocal<ArrayMap<Animator, AnimationInfo>> sRunningAnimators =
    116             new ThreadLocal<ArrayMap<Animator, AnimationInfo>>();
    117 
    118     // Scene Root is set at createAnimator() time in the cloned Transition
    119     ViewGroup mSceneRoot = null;
    120 
    121     // Whether removing views from their parent is possible. This is only for views
    122     // in the start scene, which are no longer in the view hierarchy. This property
    123     // is determined by whether the previous Scene was created from a layout
    124     // resource, and thus the views from the exited scene are going away anyway
    125     // and can be removed as necessary to achieve a particular effect, such as
    126     // removing them from parents to add them to overlays.
    127     boolean mCanRemoveViews = false;
    128 
    129     // Track all animators in use in case the transition gets canceled and needs to
    130     // cancel running animators
    131     private ArrayList<Animator> mCurrentAnimators = new ArrayList<Animator>();
    132 
    133     // Number of per-target instances of this Transition currently running. This count is
    134     // determined by calls to start() and end()
    135     int mNumInstances = 0;
    136 
    137     // Whether this transition is currently paused, due to a call to pause()
    138     boolean mPaused = false;
    139 
    140     // Whether this transition has ended. Used to avoid pause/resume on transitions
    141     // that have completed
    142     private boolean mEnded = false;
    143 
    144     // The set of listeners to be sent transition lifecycle events.
    145     ArrayList<TransitionListener> mListeners = null;
    146 
    147     // The set of animators collected from calls to createAnimator(),
    148     // to be run in runAnimators()
    149     ArrayList<Animator> mAnimators = new ArrayList<Animator>();
    150 
    151     /**
    152      * Constructs a Transition object with no target objects. A transition with
    153      * no targets defaults to running on all target objects in the scene hierarchy
    154      * (if the transition is not contained in a TransitionSet), or all target
    155      * objects passed down from its parent (if it is in a TransitionSet).
    156      */
    157     public Transition() {}
    158 
    159     /**
    160      * Sets the duration of this transition. By default, there is no duration
    161      * (indicated by a negative number), which means that the Animator created by
    162      * the transition will have its own specified duration. If the duration of a
    163      * Transition is set, that duration will override the Animator duration.
    164      *
    165      * @param duration The length of the animation, in milliseconds.
    166      * @return This transition object.
    167      * @attr ref android.R.styleable#Transition_duration
    168      */
    169     public Transition setDuration(long duration) {
    170         mDuration = duration;
    171         return this;
    172     }
    173 
    174     /**
    175      * Returns the duration set on this transition. If no duration has been set,
    176      * the returned value will be negative, indicating that resulting animators will
    177      * retain their own durations.
    178      *
    179      * @return The duration set on this transition, in milliseconds, if one has been
    180      * set, otherwise returns a negative number.
    181      */
    182     public long getDuration() {
    183         return mDuration;
    184     }
    185 
    186     /**
    187      * Sets the startDelay of this transition. By default, there is no delay
    188      * (indicated by a negative number), which means that the Animator created by
    189      * the transition will have its own specified startDelay. If the delay of a
    190      * Transition is set, that delay will override the Animator delay.
    191      *
    192      * @param startDelay The length of the delay, in milliseconds.
    193      * @return This transition object.
    194      * @attr ref android.R.styleable#Transition_startDelay
    195      */
    196     public Transition setStartDelay(long startDelay) {
    197         mStartDelay = startDelay;
    198         return this;
    199     }
    200 
    201     /**
    202      * Returns the startDelay set on this transition. If no startDelay has been set,
    203      * the returned value will be negative, indicating that resulting animators will
    204      * retain their own startDelays.
    205      *
    206      * @return The startDelay set on this transition, in milliseconds, if one has
    207      * been set, otherwise returns a negative number.
    208      */
    209     public long getStartDelay() {
    210         return mStartDelay;
    211     }
    212 
    213     /**
    214      * Sets the interpolator of this transition. By default, the interpolator
    215      * is null, which means that the Animator created by the transition
    216      * will have its own specified interpolator. If the interpolator of a
    217      * Transition is set, that interpolator will override the Animator interpolator.
    218      *
    219      * @param interpolator The time interpolator used by the transition
    220      * @return This transition object.
    221      * @attr ref android.R.styleable#Transition_interpolator
    222      */
    223     public Transition setInterpolator(TimeInterpolator interpolator) {
    224         mInterpolator = interpolator;
    225         return this;
    226     }
    227 
    228     /**
    229      * Returns the interpolator set on this transition. If no interpolator has been set,
    230      * the returned value will be null, indicating that resulting animators will
    231      * retain their own interpolators.
    232      *
    233      * @return The interpolator set on this transition, if one has been set, otherwise
    234      * returns null.
    235      */
    236     public TimeInterpolator getInterpolator() {
    237         return mInterpolator;
    238     }
    239 
    240     /**
    241      * Returns the set of property names used stored in the {@link TransitionValues}
    242      * object passed into {@link #captureStartValues(TransitionValues)} that
    243      * this transition cares about for the purposes of canceling overlapping animations.
    244      * When any transition is started on a given scene root, all transitions
    245      * currently running on that same scene root are checked to see whether the
    246      * properties on which they based their animations agree with the end values of
    247      * the same properties in the new transition. If the end values are not equal,
    248      * then the old animation is canceled since the new transition will start a new
    249      * animation to these new values. If the values are equal, the old animation is
    250      * allowed to continue and no new animation is started for that transition.
    251      *
    252      * <p>A transition does not need to override this method. However, not doing so
    253      * will mean that the cancellation logic outlined in the previous paragraph
    254      * will be skipped for that transition, possibly leading to artifacts as
    255      * old transitions and new transitions on the same targets run in parallel,
    256      * animating views toward potentially different end values.</p>
    257      *
    258      * @return An array of property names as described in the class documentation for
    259      * {@link TransitionValues}. The default implementation returns <code>null</code>.
    260      */
    261     public String[] getTransitionProperties() {
    262         return null;
    263     }
    264 
    265     /**
    266      * This method creates an animation that will be run for this transition
    267      * given the information in the startValues and endValues structures captured
    268      * earlier for the start and end scenes. Subclasses of Transition should override
    269      * this method. The method should only be called by the transition system; it is
    270      * not intended to be called from external classes.
    271      *
    272      * <p>This method is called by the transition's parent (all the way up to the
    273      * topmost Transition in the hierarchy) with the sceneRoot and start/end
    274      * values that the transition may need to set up initial target values
    275      * and construct an appropriate animation. For example, if an overall
    276      * Transition is a {@link TransitionSet} consisting of several
    277      * child transitions in sequence, then some of the child transitions may
    278      * want to set initial values on target views prior to the overall
    279      * Transition commencing, to put them in an appropriate state for the
    280      * delay between that start and the child Transition start time. For
    281      * example, a transition that fades an item in may wish to set the starting
    282      * alpha value to 0, to avoid it blinking in prior to the transition
    283      * actually starting the animation. This is necessary because the scene
    284      * change that triggers the Transition will automatically set the end-scene
    285      * on all target views, so a Transition that wants to animate from a
    286      * different value should set that value prior to returning from this method.</p>
    287      *
    288      * <p>Additionally, a Transition can perform logic to determine whether
    289      * the transition needs to run on the given target and start/end values.
    290      * For example, a transition that resizes objects on the screen may wish
    291      * to avoid running for views which are not present in either the start
    292      * or end scenes.</p>
    293      *
    294      * <p>If there is an animator created and returned from this method, the
    295      * transition mechanism will apply any applicable duration, startDelay,
    296      * and interpolator to that animation and start it. A return value of
    297      * <code>null</code> indicates that no animation should run. The default
    298      * implementation returns null.</p>
    299      *
    300      * <p>The method is called for every applicable target object, which is
    301      * stored in the {@link TransitionValues#view} field.</p>
    302      *
    303      *
    304      * @param sceneRoot The root of the transition hierarchy.
    305      * @param startValues The values for a specific target in the start scene.
    306      * @param endValues The values for the target in the end scene.
    307      * @return A Animator to be started at the appropriate time in the
    308      * overall transition for this scene change. A null value means no animation
    309      * should be run.
    310      */
    311     public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
    312             TransitionValues endValues) {
    313         return null;
    314     }
    315 
    316     /**
    317      * This method, essentially a wrapper around all calls to createAnimator for all
    318      * possible target views, is called with the entire set of start/end
    319      * values. The implementation in Transition iterates through these lists
    320      * and calls {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
    321      * with each set of start/end values on this transition. The
    322      * TransitionSet subclass overrides this method and delegates it to
    323      * each of its children in succession.
    324      *
    325      * @hide
    326      */
    327     protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
    328             TransitionValuesMaps endValues) {
    329         if (DBG) {
    330             Log.d(LOG_TAG, "createAnimators() for " + this);
    331         }
    332         ArrayMap<View, TransitionValues> endCopy =
    333                 new ArrayMap<View, TransitionValues>(endValues.viewValues);
    334         SparseArray<TransitionValues> endIdCopy =
    335                 new SparseArray<TransitionValues>(endValues.idValues.size());
    336         for (int i = 0; i < endValues.idValues.size(); ++i) {
    337             int id = endValues.idValues.keyAt(i);
    338             endIdCopy.put(id, endValues.idValues.valueAt(i));
    339         }
    340         LongSparseArray<TransitionValues> endItemIdCopy =
    341                 new LongSparseArray<TransitionValues>(endValues.itemIdValues.size());
    342         for (int i = 0; i < endValues.itemIdValues.size(); ++i) {
    343             long id = endValues.itemIdValues.keyAt(i);
    344             endItemIdCopy.put(id, endValues.itemIdValues.valueAt(i));
    345         }
    346         // Walk through the start values, playing everything we find
    347         // Remove from the end set as we go
    348         ArrayList<TransitionValues> startValuesList = new ArrayList<TransitionValues>();
    349         ArrayList<TransitionValues> endValuesList = new ArrayList<TransitionValues>();
    350         for (View view : startValues.viewValues.keySet()) {
    351             TransitionValues start = null;
    352             TransitionValues end = null;
    353             boolean isInListView = false;
    354             if (view.getParent() instanceof ListView) {
    355                 isInListView = true;
    356             }
    357             if (!isInListView) {
    358                 int id = view.getId();
    359                 start = startValues.viewValues.get(view) != null ?
    360                         startValues.viewValues.get(view) : startValues.idValues.get(id);
    361                 if (endValues.viewValues.get(view) != null) {
    362                     end = endValues.viewValues.get(view);
    363                     endCopy.remove(view);
    364                 } else if (id != View.NO_ID) {
    365                     end = endValues.idValues.get(id);
    366                     View removeView = null;
    367                     for (View viewToRemove : endCopy.keySet()) {
    368                         if (viewToRemove.getId() == id) {
    369                             removeView = viewToRemove;
    370                         }
    371                     }
    372                     if (removeView != null) {
    373                         endCopy.remove(removeView);
    374                     }
    375                 }
    376                 endIdCopy.remove(id);
    377                 if (isValidTarget(view, id)) {
    378                     startValuesList.add(start);
    379                     endValuesList.add(end);
    380                 }
    381             } else {
    382                 ListView parent = (ListView) view.getParent();
    383                 if (parent.getAdapter().hasStableIds()) {
    384                     int position = parent.getPositionForView(view);
    385                     long itemId = parent.getItemIdAtPosition(position);
    386                     start = startValues.itemIdValues.get(itemId);
    387                     endItemIdCopy.remove(itemId);
    388                     // TODO: deal with targetIDs for itemIDs for ListView items
    389                     startValuesList.add(start);
    390                     endValuesList.add(end);
    391                 }
    392             }
    393         }
    394         int startItemIdCopySize = startValues.itemIdValues.size();
    395         for (int i = 0; i < startItemIdCopySize; ++i) {
    396             long id = startValues.itemIdValues.keyAt(i);
    397             if (isValidTarget(null, id)) {
    398                 TransitionValues start = startValues.itemIdValues.get(id);
    399                 TransitionValues end = endValues.itemIdValues.get(id);
    400                 endItemIdCopy.remove(id);
    401                 startValuesList.add(start);
    402                 endValuesList.add(end);
    403             }
    404         }
    405         // Now walk through the remains of the end set
    406         for (View view : endCopy.keySet()) {
    407             int id = view.getId();
    408             if (isValidTarget(view, id)) {
    409                 TransitionValues start = startValues.viewValues.get(view) != null ?
    410                         startValues.viewValues.get(view) : startValues.idValues.get(id);
    411                 TransitionValues end = endCopy.get(view);
    412                 endIdCopy.remove(id);
    413                 startValuesList.add(start);
    414                 endValuesList.add(end);
    415             }
    416         }
    417         int endIdCopySize = endIdCopy.size();
    418         for (int i = 0; i < endIdCopySize; ++i) {
    419             int id = endIdCopy.keyAt(i);
    420             if (isValidTarget(null, id)) {
    421                 TransitionValues start = startValues.idValues.get(id);
    422                 TransitionValues end = endIdCopy.get(id);
    423                 startValuesList.add(start);
    424                 endValuesList.add(end);
    425             }
    426         }
    427         int endItemIdCopySize = endItemIdCopy.size();
    428         for (int i = 0; i < endItemIdCopySize; ++i) {
    429             long id = endItemIdCopy.keyAt(i);
    430             // TODO: Deal with targetIDs and itemIDs
    431             TransitionValues start = startValues.itemIdValues.get(id);
    432             TransitionValues end = endItemIdCopy.get(id);
    433             startValuesList.add(start);
    434             endValuesList.add(end);
    435         }
    436         ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
    437         for (int i = 0; i < startValuesList.size(); ++i) {
    438             TransitionValues start = startValuesList.get(i);
    439             TransitionValues end = endValuesList.get(i);
    440             // Only bother trying to animate with values that differ between start/end
    441             if (start != null || end != null) {
    442                 if (start == null || !start.equals(end)) {
    443                     if (DBG) {
    444                         View view = (end != null) ? end.view : start.view;
    445                         Log.d(LOG_TAG, "  differing start/end values for view " +
    446                                 view);
    447                         if (start == null || end == null) {
    448                             Log.d(LOG_TAG, "    " + ((start == null) ?
    449                                     "start null, end non-null" : "start non-null, end null"));
    450                         } else {
    451                             for (String key : start.values.keySet()) {
    452                                 Object startValue = start.values.get(key);
    453                                 Object endValue = end.values.get(key);
    454                                 if (startValue != endValue && !startValue.equals(endValue)) {
    455                                     Log.d(LOG_TAG, "    " + key + ": start(" + startValue +
    456                                             "), end(" + endValue +")");
    457                                 }
    458                             }
    459                         }
    460                     }
    461                     // TODO: what to do about targetIds and itemIds?
    462                     Animator animator = createAnimator(sceneRoot, start, end);
    463                     if (animator != null) {
    464                         // Save animation info for future cancellation purposes
    465                         View view = null;
    466                         TransitionValues infoValues = null;
    467                         if (end != null) {
    468                             view = end.view;
    469                             String[] properties = getTransitionProperties();
    470                             if (view != null && properties != null && properties.length > 0) {
    471                                 infoValues = new TransitionValues();
    472                                 infoValues.view = view;
    473                                 TransitionValues newValues = endValues.viewValues.get(view);
    474                                 if (newValues != null) {
    475                                     for (int j = 0; j < properties.length; ++j) {
    476                                         infoValues.values.put(properties[j],
    477                                                 newValues.values.get(properties[j]));
    478                                     }
    479                                 }
    480                                 int numExistingAnims = runningAnimators.size();
    481                                 for (int j = 0; j < numExistingAnims; ++j) {
    482                                     Animator anim = runningAnimators.keyAt(j);
    483                                     AnimationInfo info = runningAnimators.get(anim);
    484                                     if (info.values != null && info.view == view &&
    485                                             ((info.name == null && getName() == null) ||
    486                                             info.name.equals(getName()))) {
    487                                         if (info.values.equals(infoValues)) {
    488                                             // Favor the old animator
    489                                             animator = null;
    490                                             break;
    491                                         }
    492                                     }
    493                                 }
    494                             }
    495                         } else {
    496                             view = (start != null) ? start.view : null;
    497                         }
    498                         if (animator != null) {
    499                             AnimationInfo info = new AnimationInfo(view, getName(), infoValues);
    500                             runningAnimators.put(animator, info);
    501                             mAnimators.add(animator);
    502                         }
    503                     }
    504                 }
    505             }
    506         }
    507     }
    508 
    509     /**
    510      * Internal utility method for checking whether a given view/id
    511      * is valid for this transition, where "valid" means that either
    512      * the Transition has no target/targetId list (the default, in which
    513      * cause the transition should act on all views in the hiearchy), or
    514      * the given view is in the target list or the view id is in the
    515      * targetId list. If the target parameter is null, then the target list
    516      * is not checked (this is in the case of ListView items, where the
    517      * views are ignored and only the ids are used).
    518      */
    519     boolean isValidTarget(View target, long targetId) {
    520         if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) {
    521             return false;
    522         }
    523         if (mTargetExcludes != null && mTargetExcludes.contains(target)) {
    524             return false;
    525         }
    526         if (mTargetTypeExcludes != null && target != null) {
    527             int numTypes = mTargetTypeExcludes.size();
    528             for (int i = 0; i < numTypes; ++i) {
    529                 Class type = mTargetTypeExcludes.get(i);
    530                 if (type.isInstance(target)) {
    531                     return false;
    532                 }
    533             }
    534         }
    535         if (mTargetIds.size() == 0 && mTargets.size() == 0) {
    536             return true;
    537         }
    538         if (mTargetIds.size() > 0) {
    539             for (int i = 0; i < mTargetIds.size(); ++i) {
    540                 if (mTargetIds.get(i) == targetId) {
    541                     return true;
    542                 }
    543             }
    544         }
    545         if (target != null && mTargets.size() > 0) {
    546             for (int i = 0; i < mTargets.size(); ++i) {
    547                 if (mTargets.get(i) == target) {
    548                     return true;
    549                 }
    550             }
    551         }
    552         return false;
    553     }
    554 
    555     private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() {
    556         ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get();
    557         if (runningAnimators == null) {
    558             runningAnimators = new ArrayMap<Animator, AnimationInfo>();
    559             sRunningAnimators.set(runningAnimators);
    560         }
    561         return runningAnimators;
    562     }
    563 
    564     /**
    565      * This is called internally once all animations have been set up by the
    566      * transition hierarchy. \
    567      *
    568      * @hide
    569      */
    570     protected void runAnimators() {
    571         if (DBG) {
    572             Log.d(LOG_TAG, "runAnimators() on " + this);
    573         }
    574         start();
    575         ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
    576         // Now start every Animator that was previously created for this transition
    577         for (Animator anim : mAnimators) {
    578             if (DBG) {
    579                 Log.d(LOG_TAG, "  anim: " + anim);
    580             }
    581             if (runningAnimators.containsKey(anim)) {
    582                 start();
    583                 runAnimator(anim, runningAnimators);
    584             }
    585         }
    586         mAnimators.clear();
    587         end();
    588     }
    589 
    590     private void runAnimator(Animator animator,
    591             final ArrayMap<Animator, AnimationInfo> runningAnimators) {
    592         if (animator != null) {
    593             // TODO: could be a single listener instance for all of them since it uses the param
    594             animator.addListener(new AnimatorListenerAdapter() {
    595                 @Override
    596                 public void onAnimationStart(Animator animation) {
    597                     mCurrentAnimators.add(animation);
    598                 }
    599                 @Override
    600                 public void onAnimationEnd(Animator animation) {
    601                     runningAnimators.remove(animation);
    602                     mCurrentAnimators.remove(animation);
    603                 }
    604             });
    605             animate(animator);
    606         }
    607     }
    608 
    609     /**
    610      * Captures the values in the start scene for the properties that this
    611      * transition monitors. These values are then passed as the startValues
    612      * structure in a later call to
    613      * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
    614      * The main concern for an implementation is what the
    615      * properties are that the transition cares about and what the values are
    616      * for all of those properties. The start and end values will be compared
    617      * later during the
    618      * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
    619      * method to determine what, if any, animations, should be run.
    620      *
    621      * <p>Subclasses must implement this method. The method should only be called by the
    622      * transition system; it is not intended to be called from external classes.</p>
    623      *
    624      * @param transitionValues The holder for any values that the Transition
    625      * wishes to store. Values are stored in the <code>values</code> field
    626      * of this TransitionValues object and are keyed from
    627      * a String value. For example, to store a view's rotation value,
    628      * a transition might call
    629      * <code>transitionValues.values.put("appname:transitionname:rotation",
    630      * view.getRotation())</code>. The target view will already be stored in
    631      * the transitionValues structure when this method is called.
    632      *
    633      * @see #captureEndValues(TransitionValues)
    634      * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
    635      */
    636     public abstract void captureStartValues(TransitionValues transitionValues);
    637 
    638     /**
    639      * Captures the values in the end scene for the properties that this
    640      * transition monitors. These values are then passed as the endValues
    641      * structure in a later call to
    642      * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
    643      * The main concern for an implementation is what the
    644      * properties are that the transition cares about and what the values are
    645      * for all of those properties. The start and end values will be compared
    646      * later during the
    647      * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
    648      * method to determine what, if any, animations, should be run.
    649      *
    650      * <p>Subclasses must implement this method. The method should only be called by the
    651      * transition system; it is not intended to be called from external classes.</p>
    652      *
    653      * @param transitionValues The holder for any values that the Transition
    654      * wishes to store. Values are stored in the <code>values</code> field
    655      * of this TransitionValues object and are keyed from
    656      * a String value. For example, to store a view's rotation value,
    657      * a transition might call
    658      * <code>transitionValues.values.put("appname:transitionname:rotation",
    659      * view.getRotation())</code>. The target view will already be stored in
    660      * the transitionValues structure when this method is called.
    661      *
    662      * @see #captureStartValues(TransitionValues)
    663      * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
    664      */
    665     public abstract void captureEndValues(TransitionValues transitionValues);
    666 
    667     /**
    668      * Adds the id of a target view that this Transition is interested in
    669      * animating. By default, there are no targetIds, and a Transition will
    670      * listen for changes on every view in the hierarchy below the sceneRoot
    671      * of the Scene being transitioned into. Setting targetIds constrains
    672      * the Transition to only listen for, and act on, views with these IDs.
    673      * Views with different IDs, or no IDs whatsoever, will be ignored.
    674      *
    675      * <p>Note that using ids to specify targets implies that ids should be unique
    676      * within the view hierarchy underneat the scene root.</p>
    677      *
    678      * @see View#getId()
    679      * @param targetId The id of a target view, must be a positive number.
    680      * @return The Transition to which the targetId is added.
    681      * Returning the same object makes it easier to chain calls during
    682      * construction, such as
    683      * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code>
    684      */
    685     public Transition addTarget(int targetId) {
    686         if (targetId > 0) {
    687             mTargetIds.add(targetId);
    688         }
    689         return this;
    690     }
    691 
    692     /**
    693      * Removes the given targetId from the list of ids that this Transition
    694      * is interested in animating.
    695      *
    696      * @param targetId The id of a target view, must be a positive number.
    697      * @return The Transition from which the targetId is removed.
    698      * Returning the same object makes it easier to chain calls during
    699      * construction, such as
    700      * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code>
    701      */
    702     public Transition removeTarget(int targetId) {
    703         if (targetId > 0) {
    704             mTargetIds.remove(targetId);
    705         }
    706         return this;
    707     }
    708 
    709     /**
    710      * Whether to add the given id to the list of target ids to exclude from this
    711      * transition. The <code>exclude</code> parameter specifies whether the target
    712      * should be added to or removed from the excluded list.
    713      *
    714      * <p>Excluding targets is a general mechanism for allowing transitions to run on
    715      * a view hierarchy while skipping target views that should not be part of
    716      * the transition. For example, you may want to avoid animating children
    717      * of a specific ListView or Spinner. Views can be excluded either by their
    718      * id, or by their instance reference, or by the Class of that view
    719      * (eg, {@link Spinner}).</p>
    720      *
    721      * @see #excludeChildren(int, boolean)
    722      * @see #excludeTarget(View, boolean)
    723      * @see #excludeTarget(Class, boolean)
    724      *
    725      * @param targetId The id of a target to ignore when running this transition.
    726      * @param exclude Whether to add the target to or remove the target from the
    727      * current list of excluded targets.
    728      * @return This transition object.
    729      */
    730     public Transition excludeTarget(int targetId, boolean exclude) {
    731         mTargetIdExcludes = excludeId(mTargetIdExcludes, targetId, exclude);
    732         return this;
    733     }
    734 
    735     /**
    736      * Whether to add the children of the given id to the list of targets to exclude
    737      * from this transition. The <code>exclude</code> parameter specifies whether
    738      * the children of the target should be added to or removed from the excluded list.
    739      * Excluding children in this way provides a simple mechanism for excluding all
    740      * children of specific targets, rather than individually excluding each
    741      * child individually.
    742      *
    743      * <p>Excluding targets is a general mechanism for allowing transitions to run on
    744      * a view hierarchy while skipping target views that should not be part of
    745      * the transition. For example, you may want to avoid animating children
    746      * of a specific ListView or Spinner. Views can be excluded either by their
    747      * id, or by their instance reference, or by the Class of that view
    748      * (eg, {@link Spinner}).</p>
    749      *
    750      * @see #excludeTarget(int, boolean)
    751      * @see #excludeChildren(View, boolean)
    752      * @see #excludeChildren(Class, boolean)
    753      *
    754      * @param targetId The id of a target whose children should be ignored when running
    755      * this transition.
    756      * @param exclude Whether to add the target to or remove the target from the
    757      * current list of excluded-child targets.
    758      * @return This transition object.
    759      */
    760     public Transition excludeChildren(int targetId, boolean exclude) {
    761         mTargetIdChildExcludes = excludeId(mTargetIdChildExcludes, targetId, exclude);
    762         return this;
    763     }
    764 
    765     /**
    766      * Utility method to manage the boilerplate code that is the same whether we
    767      * are excluding targets or their children.
    768      */
    769     private ArrayList<Integer> excludeId(ArrayList<Integer> list, int targetId, boolean exclude) {
    770         if (targetId > 0) {
    771             if (exclude) {
    772                 list = ArrayListManager.add(list, targetId);
    773             } else {
    774                 list = ArrayListManager.remove(list, targetId);
    775             }
    776         }
    777         return list;
    778     }
    779 
    780     /**
    781      * Whether to add the given target to the list of targets to exclude from this
    782      * transition. The <code>exclude</code> parameter specifies whether the target
    783      * should be added to or removed from the excluded list.
    784      *
    785      * <p>Excluding targets is a general mechanism for allowing transitions to run on
    786      * a view hierarchy while skipping target views that should not be part of
    787      * the transition. For example, you may want to avoid animating children
    788      * of a specific ListView or Spinner. Views can be excluded either by their
    789      * id, or by their instance reference, or by the Class of that view
    790      * (eg, {@link Spinner}).</p>
    791      *
    792      * @see #excludeChildren(View, boolean)
    793      * @see #excludeTarget(int, boolean)
    794      * @see #excludeTarget(Class, boolean)
    795      *
    796      * @param target The target to ignore when running this transition.
    797      * @param exclude Whether to add the target to or remove the target from the
    798      * current list of excluded targets.
    799      * @return This transition object.
    800      */
    801     public Transition excludeTarget(View target, boolean exclude) {
    802         mTargetExcludes = excludeView(mTargetExcludes, target, exclude);
    803         return this;
    804     }
    805 
    806     /**
    807      * Whether to add the children of given target to the list of target children
    808      * to exclude from this transition. The <code>exclude</code> parameter specifies
    809      * whether the target should be added to or removed from the excluded list.
    810      *
    811      * <p>Excluding targets is a general mechanism for allowing transitions to run on
    812      * a view hierarchy while skipping target views that should not be part of
    813      * the transition. For example, you may want to avoid animating children
    814      * of a specific ListView or Spinner. Views can be excluded either by their
    815      * id, or by their instance reference, or by the Class of that view
    816      * (eg, {@link Spinner}).</p>
    817      *
    818      * @see #excludeTarget(View, boolean)
    819      * @see #excludeChildren(int, boolean)
    820      * @see #excludeChildren(Class, boolean)
    821      *
    822      * @param target The target to ignore when running this transition.
    823      * @param exclude Whether to add the target to or remove the target from the
    824      * current list of excluded targets.
    825      * @return This transition object.
    826      */
    827     public Transition excludeChildren(View target, boolean exclude) {
    828         mTargetChildExcludes = excludeView(mTargetChildExcludes, target, exclude);
    829         return this;
    830     }
    831 
    832     /**
    833      * Utility method to manage the boilerplate code that is the same whether we
    834      * are excluding targets or their children.
    835      */
    836     private ArrayList<View> excludeView(ArrayList<View> list, View target, boolean exclude) {
    837         if (target != null) {
    838             if (exclude) {
    839                 list = ArrayListManager.add(list, target);
    840             } else {
    841                 list = ArrayListManager.remove(list, target);
    842             }
    843         }
    844         return list;
    845     }
    846 
    847     /**
    848      * Whether to add the given type to the list of types to exclude from this
    849      * transition. The <code>exclude</code> parameter specifies whether the target
    850      * type should be added to or removed from the excluded list.
    851      *
    852      * <p>Excluding targets is a general mechanism for allowing transitions to run on
    853      * a view hierarchy while skipping target views that should not be part of
    854      * the transition. For example, you may want to avoid animating children
    855      * of a specific ListView or Spinner. Views can be excluded either by their
    856      * id, or by their instance reference, or by the Class of that view
    857      * (eg, {@link Spinner}).</p>
    858      *
    859      * @see #excludeChildren(Class, boolean)
    860      * @see #excludeTarget(int, boolean)
    861      * @see #excludeTarget(View, boolean)
    862      *
    863      * @param type The type to ignore when running this transition.
    864      * @param exclude Whether to add the target type to or remove it from the
    865      * current list of excluded target types.
    866      * @return This transition object.
    867      */
    868     public Transition excludeTarget(Class type, boolean exclude) {
    869         mTargetTypeExcludes = excludeType(mTargetTypeExcludes, type, exclude);
    870         return this;
    871     }
    872 
    873     /**
    874      * Whether to add the given type to the list of types whose children should
    875      * be excluded from this transition. The <code>exclude</code> parameter
    876      * specifies whether the target type should be added to or removed from
    877      * the excluded list.
    878      *
    879      * <p>Excluding targets is a general mechanism for allowing transitions to run on
    880      * a view hierarchy while skipping target views that should not be part of
    881      * the transition. For example, you may want to avoid animating children
    882      * of a specific ListView or Spinner. Views can be excluded either by their
    883      * id, or by their instance reference, or by the Class of that view
    884      * (eg, {@link Spinner}).</p>
    885      *
    886      * @see #excludeTarget(Class, boolean)
    887      * @see #excludeChildren(int, boolean)
    888      * @see #excludeChildren(View, boolean)
    889      *
    890      * @param type The type to ignore when running this transition.
    891      * @param exclude Whether to add the target type to or remove it from the
    892      * current list of excluded target types.
    893      * @return This transition object.
    894      */
    895     public Transition excludeChildren(Class type, boolean exclude) {
    896         mTargetTypeChildExcludes = excludeType(mTargetTypeChildExcludes, type, exclude);
    897         return this;
    898     }
    899 
    900     /**
    901      * Utility method to manage the boilerplate code that is the same whether we
    902      * are excluding targets or their children.
    903      */
    904     private ArrayList<Class> excludeType(ArrayList<Class> list, Class type, boolean exclude) {
    905         if (type != null) {
    906             if (exclude) {
    907                 list = ArrayListManager.add(list, type);
    908             } else {
    909                 list = ArrayListManager.remove(list, type);
    910             }
    911         }
    912         return list;
    913     }
    914 
    915     /**
    916      * Sets the target view instances that this Transition is interested in
    917      * animating. By default, there are no targets, and a Transition will
    918      * listen for changes on every view in the hierarchy below the sceneRoot
    919      * of the Scene being transitioned into. Setting targets constrains
    920      * the Transition to only listen for, and act on, these views.
    921      * All other views will be ignored.
    922      *
    923      * <p>The target list is like the {@link #addTarget(int) targetId}
    924      * list except this list specifies the actual View instances, not the ids
    925      * of the views. This is an important distinction when scene changes involve
    926      * view hierarchies which have been inflated separately; different views may
    927      * share the same id but not actually be the same instance. If the transition
    928      * should treat those views as the same, then {@link #addTarget(int)} should be used
    929      * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve
    930      * changes all within the same view hierarchy, among views which do not
    931      * necessarily have ids set on them, then the target list of views may be more
    932      * convenient.</p>
    933      *
    934      * @see #addTarget(int)
    935      * @param target A View on which the Transition will act, must be non-null.
    936      * @return The Transition to which the target is added.
    937      * Returning the same object makes it easier to chain calls during
    938      * construction, such as
    939      * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code>
    940      */
    941     public Transition addTarget(View target) {
    942         mTargets.add(target);
    943         return this;
    944     }
    945 
    946     /**
    947      * Removes the given target from the list of targets that this Transition
    948      * is interested in animating.
    949      *
    950      * @param target The target view, must be non-null.
    951      * @return Transition The Transition from which the target is removed.
    952      * Returning the same object makes it easier to chain calls during
    953      * construction, such as
    954      * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code>
    955      */
    956     public Transition removeTarget(View target) {
    957         if (target != null) {
    958             mTargets.remove(target);
    959         }
    960         return this;
    961     }
    962 
    963     /**
    964      * Returns the array of target IDs that this transition limits itself to
    965      * tracking and animating. If the array is null for both this method and
    966      * {@link #getTargets()}, then this transition is
    967      * not limited to specific views, and will handle changes to any views
    968      * in the hierarchy of a scene change.
    969      *
    970      * @return the list of target IDs
    971      */
    972     public List<Integer> getTargetIds() {
    973         return mTargetIds;
    974     }
    975 
    976     /**
    977      * Returns the array of target views that this transition limits itself to
    978      * tracking and animating. If the array is null for both this method and
    979      * {@link #getTargetIds()}, then this transition is
    980      * not limited to specific views, and will handle changes to any views
    981      * in the hierarchy of a scene change.
    982      *
    983      * @return the list of target views
    984      */
    985     public List<View> getTargets() {
    986         return mTargets;
    987     }
    988 
    989     /**
    990      * Recursive method that captures values for the given view and the
    991      * hierarchy underneath it.
    992      * @param sceneRoot The root of the view hierarchy being captured
    993      * @param start true if this capture is happening before the scene change,
    994      * false otherwise
    995      */
    996     void captureValues(ViewGroup sceneRoot, boolean start) {
    997         if (start) {
    998             mStartValues.viewValues.clear();
    999             mStartValues.idValues.clear();
   1000             mStartValues.itemIdValues.clear();
   1001         } else {
   1002             mEndValues.viewValues.clear();
   1003             mEndValues.idValues.clear();
   1004             mEndValues.itemIdValues.clear();
   1005         }
   1006         if (mTargetIds.size() > 0 || mTargets.size() > 0) {
   1007             if (mTargetIds.size() > 0) {
   1008                 for (int i = 0; i < mTargetIds.size(); ++i) {
   1009                     int id = mTargetIds.get(i);
   1010                     View view = sceneRoot.findViewById(id);
   1011                     if (view != null) {
   1012                         TransitionValues values = new TransitionValues();
   1013                         values.view = view;
   1014                         if (start) {
   1015                             captureStartValues(values);
   1016                         } else {
   1017                             captureEndValues(values);
   1018                         }
   1019                         if (start) {
   1020                             mStartValues.viewValues.put(view, values);
   1021                             if (id >= 0) {
   1022                                 mStartValues.idValues.put(id, values);
   1023                             }
   1024                         } else {
   1025                             mEndValues.viewValues.put(view, values);
   1026                             if (id >= 0) {
   1027                                 mEndValues.idValues.put(id, values);
   1028                             }
   1029                         }
   1030                     }
   1031                 }
   1032             }
   1033             if (mTargets.size() > 0) {
   1034                 for (int i = 0; i < mTargets.size(); ++i) {
   1035                     View view = mTargets.get(i);
   1036                     if (view != null) {
   1037                         TransitionValues values = new TransitionValues();
   1038                         values.view = view;
   1039                         if (start) {
   1040                             captureStartValues(values);
   1041                         } else {
   1042                             captureEndValues(values);
   1043                         }
   1044                         if (start) {
   1045                             mStartValues.viewValues.put(view, values);
   1046                         } else {
   1047                             mEndValues.viewValues.put(view, values);
   1048                         }
   1049                     }
   1050                 }
   1051             }
   1052         } else {
   1053             captureHierarchy(sceneRoot, start);
   1054         }
   1055     }
   1056 
   1057     /**
   1058      * Recursive method which captures values for an entire view hierarchy,
   1059      * starting at some root view. Transitions without targetIDs will use this
   1060      * method to capture values for all possible views.
   1061      *
   1062      * @param view The view for which to capture values. Children of this View
   1063      * will also be captured, recursively down to the leaf nodes.
   1064      * @param start true if values are being captured in the start scene, false
   1065      * otherwise.
   1066      */
   1067     private void captureHierarchy(View view, boolean start) {
   1068         if (view == null) {
   1069             return;
   1070         }
   1071         boolean isListViewItem = false;
   1072         if (view.getParent() instanceof ListView) {
   1073             isListViewItem = true;
   1074         }
   1075         if (isListViewItem && !((ListView) view.getParent()).getAdapter().hasStableIds()) {
   1076             // ignore listview children unless we can track them with stable IDs
   1077             return;
   1078         }
   1079         int id = View.NO_ID;
   1080         long itemId = View.NO_ID;
   1081         if (!isListViewItem) {
   1082             id = view.getId();
   1083         } else {
   1084             ListView listview = (ListView) view.getParent();
   1085             int position = listview.getPositionForView(view);
   1086             itemId = listview.getItemIdAtPosition(position);
   1087             view.setHasTransientState(true);
   1088         }
   1089         if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) {
   1090             return;
   1091         }
   1092         if (mTargetExcludes != null && mTargetExcludes.contains(view)) {
   1093             return;
   1094         }
   1095         if (mTargetTypeExcludes != null && view != null) {
   1096             int numTypes = mTargetTypeExcludes.size();
   1097             for (int i = 0; i < numTypes; ++i) {
   1098                 if (mTargetTypeExcludes.get(i).isInstance(view)) {
   1099                     return;
   1100                 }
   1101             }
   1102         }
   1103         TransitionValues values = new TransitionValues();
   1104         values.view = view;
   1105         if (start) {
   1106             captureStartValues(values);
   1107         } else {
   1108             captureEndValues(values);
   1109         }
   1110         if (start) {
   1111             if (!isListViewItem) {
   1112                 mStartValues.viewValues.put(view, values);
   1113                 if (id >= 0) {
   1114                     mStartValues.idValues.put((int) id, values);
   1115                 }
   1116             } else {
   1117                 mStartValues.itemIdValues.put(itemId, values);
   1118             }
   1119         } else {
   1120             if (!isListViewItem) {
   1121                 mEndValues.viewValues.put(view, values);
   1122                 if (id >= 0) {
   1123                     mEndValues.idValues.put((int) id, values);
   1124                 }
   1125             } else {
   1126                 mEndValues.itemIdValues.put(itemId, values);
   1127             }
   1128         }
   1129         if (view instanceof ViewGroup) {
   1130             // Don't traverse child hierarchy if there are any child-excludes on this view
   1131             if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) {
   1132                 return;
   1133             }
   1134             if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) {
   1135                 return;
   1136             }
   1137             if (mTargetTypeChildExcludes != null && view != null) {
   1138                 int numTypes = mTargetTypeChildExcludes.size();
   1139                 for (int i = 0; i < numTypes; ++i) {
   1140                     if (mTargetTypeChildExcludes.get(i).isInstance(view)) {
   1141                         return;
   1142                     }
   1143                 }
   1144             }
   1145             ViewGroup parent = (ViewGroup) view;
   1146             for (int i = 0; i < parent.getChildCount(); ++i) {
   1147                 captureHierarchy(parent.getChildAt(i), start);
   1148             }
   1149         }
   1150     }
   1151 
   1152     /**
   1153      * This method can be called by transitions to get the TransitionValues for
   1154      * any particular view during the transition-playing process. This might be
   1155      * necessary, for example, to query the before/after state of related views
   1156      * for a given transition.
   1157      */
   1158     public TransitionValues getTransitionValues(View view, boolean start) {
   1159         if (mParent != null) {
   1160             return mParent.getTransitionValues(view, start);
   1161         }
   1162         TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
   1163         TransitionValues values = valuesMaps.viewValues.get(view);
   1164         if (values == null) {
   1165             int id = view.getId();
   1166             if (id >= 0) {
   1167                 values = valuesMaps.idValues.get(id);
   1168             }
   1169             if (values == null && view.getParent() instanceof ListView) {
   1170                 ListView listview = (ListView) view.getParent();
   1171                 int position = listview.getPositionForView(view);
   1172                 long itemId = listview.getItemIdAtPosition(position);
   1173                 values = valuesMaps.itemIdValues.get(itemId);
   1174             }
   1175             // TODO: Doesn't handle the case where a view was parented to a
   1176             // ListView (with an itemId), but no longer is
   1177         }
   1178         return values;
   1179     }
   1180 
   1181     /**
   1182      * Pauses this transition, sending out calls to {@link
   1183      * TransitionListener#onTransitionPause(Transition)} to all listeners
   1184      * and pausing all running animators started by this transition.
   1185      *
   1186      * @hide
   1187      */
   1188     public void pause() {
   1189         if (!mEnded) {
   1190             ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
   1191             int numOldAnims = runningAnimators.size();
   1192             for (int i = numOldAnims - 1; i >= 0; i--) {
   1193                 Animator anim = runningAnimators.keyAt(i);
   1194                 anim.pause();
   1195             }
   1196             if (mListeners != null && mListeners.size() > 0) {
   1197                 ArrayList<TransitionListener> tmpListeners =
   1198                         (ArrayList<TransitionListener>) mListeners.clone();
   1199                 int numListeners = tmpListeners.size();
   1200                 for (int i = 0; i < numListeners; ++i) {
   1201                     tmpListeners.get(i).onTransitionPause(this);
   1202                 }
   1203             }
   1204             mPaused = true;
   1205         }
   1206     }
   1207 
   1208     /**
   1209      * Resumes this transition, sending out calls to {@link
   1210      * TransitionListener#onTransitionPause(Transition)} to all listeners
   1211      * and pausing all running animators started by this transition.
   1212      *
   1213      * @hide
   1214      */
   1215     public void resume() {
   1216         if (mPaused) {
   1217             if (!mEnded) {
   1218                 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
   1219                 int numOldAnims = runningAnimators.size();
   1220                 for (int i = numOldAnims - 1; i >= 0; i--) {
   1221                     Animator anim = runningAnimators.keyAt(i);
   1222                     anim.resume();
   1223                 }
   1224                 if (mListeners != null && mListeners.size() > 0) {
   1225                     ArrayList<TransitionListener> tmpListeners =
   1226                             (ArrayList<TransitionListener>) mListeners.clone();
   1227                     int numListeners = tmpListeners.size();
   1228                     for (int i = 0; i < numListeners; ++i) {
   1229                         tmpListeners.get(i).onTransitionResume(this);
   1230                     }
   1231                 }
   1232             }
   1233             mPaused = false;
   1234         }
   1235     }
   1236 
   1237     /**
   1238      * Called by TransitionManager to play the transition. This calls
   1239      * createAnimators() to set things up and create all of the animations and then
   1240      * runAnimations() to actually start the animations.
   1241      */
   1242     void playTransition(ViewGroup sceneRoot) {
   1243         ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
   1244         int numOldAnims = runningAnimators.size();
   1245         for (int i = numOldAnims - 1; i >= 0; i--) {
   1246             Animator anim = runningAnimators.keyAt(i);
   1247             if (anim != null) {
   1248                 AnimationInfo oldInfo = runningAnimators.get(anim);
   1249                 if (oldInfo != null) {
   1250                     boolean cancel = false;
   1251                     TransitionValues oldValues = oldInfo.values;
   1252                     View oldView = oldInfo.view;
   1253                     TransitionValues newValues = mEndValues.viewValues != null ?
   1254                             mEndValues.viewValues.get(oldView) : null;
   1255                     if (newValues == null) {
   1256                         newValues = mEndValues.idValues.get(oldView.getId());
   1257                     }
   1258                     if (oldValues != null) {
   1259                         // if oldValues null, then transition didn't care to stash values,
   1260                         // and won't get canceled
   1261                         if (newValues != null) {
   1262                             for (String key : oldValues.values.keySet()) {
   1263                                 Object oldValue = oldValues.values.get(key);
   1264                                 Object newValue = newValues.values.get(key);
   1265                                 if (oldValue != null && newValue != null &&
   1266                                         !oldValue.equals(newValue)) {
   1267                                     cancel = true;
   1268                                     if (DBG) {
   1269                                         Log.d(LOG_TAG, "Transition.playTransition: " +
   1270                                                 "oldValue != newValue for " + key +
   1271                                                 ": old, new = " + oldValue + ", " + newValue);
   1272                                     }
   1273                                     break;
   1274                                 }
   1275                             }
   1276                         }
   1277                     }
   1278                     if (cancel) {
   1279                         if (anim.isRunning() || anim.isStarted()) {
   1280                             if (DBG) {
   1281                                 Log.d(LOG_TAG, "Canceling anim " + anim);
   1282                             }
   1283                             anim.cancel();
   1284                         } else {
   1285                             if (DBG) {
   1286                                 Log.d(LOG_TAG, "removing anim from info list: " + anim);
   1287                             }
   1288                             runningAnimators.remove(anim);
   1289                         }
   1290                     }
   1291                 }
   1292             }
   1293         }
   1294 
   1295         createAnimators(sceneRoot, mStartValues, mEndValues);
   1296         runAnimators();
   1297     }
   1298 
   1299     /**
   1300      * This is a utility method used by subclasses to handle standard parts of
   1301      * setting up and running an Animator: it sets the {@link #getDuration()
   1302      * duration} and the {@link #getStartDelay() startDelay}, starts the
   1303      * animation, and, when the animator ends, calls {@link #end()}.
   1304      *
   1305      * @param animator The Animator to be run during this transition.
   1306      *
   1307      * @hide
   1308      */
   1309     protected void animate(Animator animator) {
   1310         // TODO: maybe pass auto-end as a boolean parameter?
   1311         if (animator == null) {
   1312             end();
   1313         } else {
   1314             if (getDuration() >= 0) {
   1315                 animator.setDuration(getDuration());
   1316             }
   1317             if (getStartDelay() >= 0) {
   1318                 animator.setStartDelay(getStartDelay());
   1319             }
   1320             if (getInterpolator() != null) {
   1321                 animator.setInterpolator(getInterpolator());
   1322             }
   1323             animator.addListener(new AnimatorListenerAdapter() {
   1324                 @Override
   1325                 public void onAnimationEnd(Animator animation) {
   1326                     end();
   1327                     animation.removeListener(this);
   1328                 }
   1329             });
   1330             animator.start();
   1331         }
   1332     }
   1333 
   1334     /**
   1335      * This method is called automatically by the transition and
   1336      * TransitionSet classes prior to a Transition subclass starting;
   1337      * subclasses should not need to call it directly.
   1338      *
   1339      * @hide
   1340      */
   1341     protected void start() {
   1342         if (mNumInstances == 0) {
   1343             if (mListeners != null && mListeners.size() > 0) {
   1344                 ArrayList<TransitionListener> tmpListeners =
   1345                         (ArrayList<TransitionListener>) mListeners.clone();
   1346                 int numListeners = tmpListeners.size();
   1347                 for (int i = 0; i < numListeners; ++i) {
   1348                     tmpListeners.get(i).onTransitionStart(this);
   1349                 }
   1350             }
   1351             mEnded = false;
   1352         }
   1353         mNumInstances++;
   1354     }
   1355 
   1356     /**
   1357      * This method is called automatically by the Transition and
   1358      * TransitionSet classes when a transition finishes, either because
   1359      * a transition did nothing (returned a null Animator from
   1360      * {@link Transition#createAnimator(ViewGroup, TransitionValues,
   1361      * TransitionValues)}) or because the transition returned a valid
   1362      * Animator and end() was called in the onAnimationEnd()
   1363      * callback of the AnimatorListener.
   1364      *
   1365      * @hide
   1366      */
   1367     protected void end() {
   1368         --mNumInstances;
   1369         if (mNumInstances == 0) {
   1370             if (mListeners != null && mListeners.size() > 0) {
   1371                 ArrayList<TransitionListener> tmpListeners =
   1372                         (ArrayList<TransitionListener>) mListeners.clone();
   1373                 int numListeners = tmpListeners.size();
   1374                 for (int i = 0; i < numListeners; ++i) {
   1375                     tmpListeners.get(i).onTransitionEnd(this);
   1376                 }
   1377             }
   1378             for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) {
   1379                 TransitionValues tv = mStartValues.itemIdValues.valueAt(i);
   1380                 View v = tv.view;
   1381                 if (v.hasTransientState()) {
   1382                     v.setHasTransientState(false);
   1383                 }
   1384             }
   1385             for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) {
   1386                 TransitionValues tv = mEndValues.itemIdValues.valueAt(i);
   1387                 View v = tv.view;
   1388                 if (v.hasTransientState()) {
   1389                     v.setHasTransientState(false);
   1390                 }
   1391             }
   1392             mEnded = true;
   1393         }
   1394     }
   1395 
   1396     /**
   1397      * This method cancels a transition that is currently running.
   1398      *
   1399      * @hide
   1400      */
   1401     protected void cancel() {
   1402         int numAnimators = mCurrentAnimators.size();
   1403         for (int i = numAnimators - 1; i >= 0; i--) {
   1404             Animator animator = mCurrentAnimators.get(i);
   1405             animator.cancel();
   1406         }
   1407         if (mListeners != null && mListeners.size() > 0) {
   1408             ArrayList<TransitionListener> tmpListeners =
   1409                     (ArrayList<TransitionListener>) mListeners.clone();
   1410             int numListeners = tmpListeners.size();
   1411             for (int i = 0; i < numListeners; ++i) {
   1412                 tmpListeners.get(i).onTransitionCancel(this);
   1413             }
   1414         }
   1415     }
   1416 
   1417     /**
   1418      * Adds a listener to the set of listeners that are sent events through the
   1419      * life of an animation, such as start, repeat, and end.
   1420      *
   1421      * @param listener the listener to be added to the current set of listeners
   1422      * for this animation.
   1423      * @return This transition object.
   1424      */
   1425     public Transition addListener(TransitionListener listener) {
   1426         if (mListeners == null) {
   1427             mListeners = new ArrayList<TransitionListener>();
   1428         }
   1429         mListeners.add(listener);
   1430         return this;
   1431     }
   1432 
   1433     /**
   1434      * Removes a listener from the set listening to this animation.
   1435      *
   1436      * @param listener the listener to be removed from the current set of
   1437      * listeners for this transition.
   1438      * @return This transition object.
   1439      */
   1440     public Transition removeListener(TransitionListener listener) {
   1441         if (mListeners == null) {
   1442             return this;
   1443         }
   1444         mListeners.remove(listener);
   1445         if (mListeners.size() == 0) {
   1446             mListeners = null;
   1447         }
   1448         return this;
   1449     }
   1450 
   1451     Transition setSceneRoot(ViewGroup sceneRoot) {
   1452         mSceneRoot = sceneRoot;
   1453         return this;
   1454     }
   1455 
   1456     void setCanRemoveViews(boolean canRemoveViews) {
   1457         mCanRemoveViews = canRemoveViews;
   1458     }
   1459 
   1460     @Override
   1461     public String toString() {
   1462         return toString("");
   1463     }
   1464 
   1465     @Override
   1466     public Transition clone() {
   1467         Transition clone = null;
   1468         try {
   1469             clone = (Transition) super.clone();
   1470             clone.mAnimators = new ArrayList<Animator>();
   1471             clone.mStartValues = new TransitionValuesMaps();
   1472             clone.mEndValues = new TransitionValuesMaps();
   1473         } catch (CloneNotSupportedException e) {}
   1474 
   1475         return clone;
   1476     }
   1477 
   1478     /**
   1479      * Returns the name of this Transition. This name is used internally to distinguish
   1480      * between different transitions to determine when interrupting transitions overlap.
   1481      * For example, a ChangeBounds running on the same target view as another ChangeBounds
   1482      * should determine whether the old transition is animating to different end values
   1483      * and should be canceled in favor of the new transition.
   1484      *
   1485      * <p>By default, a Transition's name is simply the value of {@link Class#getName()},
   1486      * but subclasses are free to override and return something different.</p>
   1487      *
   1488      * @return The name of this transition.
   1489      */
   1490     public String getName() {
   1491         return mName;
   1492     }
   1493 
   1494     String toString(String indent) {
   1495         String result = indent + getClass().getSimpleName() + "@" +
   1496                 Integer.toHexString(hashCode()) + ": ";
   1497         if (mDuration != -1) {
   1498             result += "dur(" + mDuration + ") ";
   1499         }
   1500         if (mStartDelay != -1) {
   1501             result += "dly(" + mStartDelay + ") ";
   1502         }
   1503         if (mInterpolator != null) {
   1504             result += "interp(" + mInterpolator + ") ";
   1505         }
   1506         if (mTargetIds.size() > 0 || mTargets.size() > 0) {
   1507             result += "tgts(";
   1508             if (mTargetIds.size() > 0) {
   1509                 for (int i = 0; i < mTargetIds.size(); ++i) {
   1510                     if (i > 0) {
   1511                         result += ", ";
   1512                     }
   1513                     result += mTargetIds.get(i);
   1514                 }
   1515             }
   1516             if (mTargets.size() > 0) {
   1517                 for (int i = 0; i < mTargets.size(); ++i) {
   1518                     if (i > 0) {
   1519                         result += ", ";
   1520                     }
   1521                     result += mTargets.get(i);
   1522                 }
   1523             }
   1524             result += ")";
   1525         }
   1526         return result;
   1527     }
   1528 
   1529     /**
   1530      * A transition listener receives notifications from a transition.
   1531      * Notifications indicate transition lifecycle events.
   1532      */
   1533     public static interface TransitionListener {
   1534         /**
   1535          * Notification about the start of the transition.
   1536          *
   1537          * @param transition The started transition.
   1538          */
   1539         void onTransitionStart(Transition transition);
   1540 
   1541         /**
   1542          * Notification about the end of the transition. Canceled transitions
   1543          * will always notify listeners of both the cancellation and end
   1544          * events. That is, {@link #onTransitionEnd(Transition)} is always called,
   1545          * regardless of whether the transition was canceled or played
   1546          * through to completion.
   1547          *
   1548          * @param transition The transition which reached its end.
   1549          */
   1550         void onTransitionEnd(Transition transition);
   1551 
   1552         /**
   1553          * Notification about the cancellation of the transition.
   1554          * Note that cancel may be called by a parent {@link TransitionSet} on
   1555          * a child transition which has not yet started. This allows the child
   1556          * transition to restore state on target objects which was set at
   1557          * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
   1558          * createAnimator()} time.
   1559          *
   1560          * @param transition The transition which was canceled.
   1561          */
   1562         void onTransitionCancel(Transition transition);
   1563 
   1564         /**
   1565          * Notification when a transition is paused.
   1566          * Note that createAnimator() may be called by a parent {@link TransitionSet} on
   1567          * a child transition which has not yet started. This allows the child
   1568          * transition to restore state on target objects which was set at
   1569          * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
   1570          * createAnimator()} time.
   1571          *
   1572          * @param transition The transition which was paused.
   1573          */
   1574         void onTransitionPause(Transition transition);
   1575 
   1576         /**
   1577          * Notification when a transition is resumed.
   1578          * Note that resume() may be called by a parent {@link TransitionSet} on
   1579          * a child transition which has not yet started. This allows the child
   1580          * transition to restore state which may have changed in an earlier call
   1581          * to {@link #onTransitionPause(Transition)}.
   1582          *
   1583          * @param transition The transition which was resumed.
   1584          */
   1585         void onTransitionResume(Transition transition);
   1586     }
   1587 
   1588     /**
   1589      * Utility adapter class to avoid having to override all three methods
   1590      * whenever someone just wants to listen for a single event.
   1591      *
   1592      * @hide
   1593      * */
   1594     public static class TransitionListenerAdapter implements TransitionListener {
   1595         @Override
   1596         public void onTransitionStart(Transition transition) {
   1597         }
   1598 
   1599         @Override
   1600         public void onTransitionEnd(Transition transition) {
   1601         }
   1602 
   1603         @Override
   1604         public void onTransitionCancel(Transition transition) {
   1605         }
   1606 
   1607         @Override
   1608         public void onTransitionPause(Transition transition) {
   1609         }
   1610 
   1611         @Override
   1612         public void onTransitionResume(Transition transition) {
   1613         }
   1614     }
   1615 
   1616     /**
   1617      * Holds information about each animator used when a new transition starts
   1618      * while other transitions are still running to determine whether a running
   1619      * animation should be canceled or a new animation noop'd. The structure holds
   1620      * information about the state that an animation is going to, to be compared to
   1621      * end state of a new animation.
   1622      */
   1623     private static class AnimationInfo {
   1624         View view;
   1625         String name;
   1626         TransitionValues values;
   1627 
   1628         AnimationInfo(View view, String name, TransitionValues values) {
   1629             this.view = view;
   1630             this.name = name;
   1631             this.values = values;
   1632         }
   1633     }
   1634 
   1635     /**
   1636      * Utility class for managing typed ArrayLists efficiently. In particular, this
   1637      * can be useful for lists that we don't expect to be used often (eg, the exclude
   1638      * lists), so we'd like to keep them nulled out by default. This causes the code to
   1639      * become tedious, with constant null checks, code to allocate when necessary,
   1640      * and code to null out the reference when the list is empty. This class encapsulates
   1641      * all of that functionality into simple add()/remove() methods which perform the
   1642      * necessary checks, allocation/null-out as appropriate, and return the
   1643      * resulting list.
   1644      */
   1645     private static class ArrayListManager {
   1646 
   1647         /**
   1648          * Add the specified item to the list, returning the resulting list.
   1649          * The returned list can either the be same list passed in or, if that
   1650          * list was null, the new list that was created.
   1651          *
   1652          * Note that the list holds unique items; if the item already exists in the
   1653          * list, the list is not modified.
   1654          */
   1655         static <T> ArrayList<T> add(ArrayList<T> list, T item) {
   1656             if (list == null) {
   1657                 list = new ArrayList<T>();
   1658             }
   1659             if (!list.contains(item)) {
   1660                 list.add(item);
   1661             }
   1662             return list;
   1663         }
   1664 
   1665         /**
   1666          * Remove the specified item from the list, returning the resulting list.
   1667          * The returned list can either the be same list passed in or, if that
   1668          * list becomes empty as a result of the remove(), the new list was created.
   1669          */
   1670         static <T> ArrayList<T> remove(ArrayList<T> list, T item) {
   1671             if (list != null) {
   1672                 list.remove(item);
   1673                 if (list.isEmpty()) {
   1674                     list = null;
   1675                 }
   1676             }
   1677             return list;
   1678         }
   1679     }
   1680 
   1681 }
   1682