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         clearValues(start);
    998         if (mTargetIds.size() > 0 || mTargets.size() > 0) {
    999             if (mTargetIds.size() > 0) {
   1000                 for (int i = 0; i < mTargetIds.size(); ++i) {
   1001                     int id = mTargetIds.get(i);
   1002                     View view = sceneRoot.findViewById(id);
   1003                     if (view != null) {
   1004                         TransitionValues values = new TransitionValues();
   1005                         values.view = view;
   1006                         if (start) {
   1007                             captureStartValues(values);
   1008                         } else {
   1009                             captureEndValues(values);
   1010                         }
   1011                         if (start) {
   1012                             mStartValues.viewValues.put(view, values);
   1013                             if (id >= 0) {
   1014                                 mStartValues.idValues.put(id, values);
   1015                             }
   1016                         } else {
   1017                             mEndValues.viewValues.put(view, values);
   1018                             if (id >= 0) {
   1019                                 mEndValues.idValues.put(id, values);
   1020                             }
   1021                         }
   1022                     }
   1023                 }
   1024             }
   1025             if (mTargets.size() > 0) {
   1026                 for (int i = 0; i < mTargets.size(); ++i) {
   1027                     View view = mTargets.get(i);
   1028                     if (view != null) {
   1029                         TransitionValues values = new TransitionValues();
   1030                         values.view = view;
   1031                         if (start) {
   1032                             captureStartValues(values);
   1033                         } else {
   1034                             captureEndValues(values);
   1035                         }
   1036                         if (start) {
   1037                             mStartValues.viewValues.put(view, values);
   1038                         } else {
   1039                             mEndValues.viewValues.put(view, values);
   1040                         }
   1041                     }
   1042                 }
   1043             }
   1044         } else {
   1045             captureHierarchy(sceneRoot, start);
   1046         }
   1047     }
   1048 
   1049     /**
   1050      * Clear valuesMaps for specified start/end state
   1051      *
   1052      * @param start true if the start values should be cleared, false otherwise
   1053      */
   1054     void clearValues(boolean start) {
   1055         if (start) {
   1056             mStartValues.viewValues.clear();
   1057             mStartValues.idValues.clear();
   1058             mStartValues.itemIdValues.clear();
   1059         } else {
   1060             mEndValues.viewValues.clear();
   1061             mEndValues.idValues.clear();
   1062             mEndValues.itemIdValues.clear();
   1063         }
   1064     }
   1065 
   1066     /**
   1067      * Recursive method which captures values for an entire view hierarchy,
   1068      * starting at some root view. Transitions without targetIDs will use this
   1069      * method to capture values for all possible views.
   1070      *
   1071      * @param view The view for which to capture values. Children of this View
   1072      * will also be captured, recursively down to the leaf nodes.
   1073      * @param start true if values are being captured in the start scene, false
   1074      * otherwise.
   1075      */
   1076     private void captureHierarchy(View view, boolean start) {
   1077         if (view == null) {
   1078             return;
   1079         }
   1080         boolean isListViewItem = false;
   1081         if (view.getParent() instanceof ListView) {
   1082             isListViewItem = true;
   1083         }
   1084         if (isListViewItem && !((ListView) view.getParent()).getAdapter().hasStableIds()) {
   1085             // ignore listview children unless we can track them with stable IDs
   1086             return;
   1087         }
   1088         int id = View.NO_ID;
   1089         long itemId = View.NO_ID;
   1090         if (!isListViewItem) {
   1091             id = view.getId();
   1092         } else {
   1093             ListView listview = (ListView) view.getParent();
   1094             int position = listview.getPositionForView(view);
   1095             itemId = listview.getItemIdAtPosition(position);
   1096             view.setHasTransientState(true);
   1097         }
   1098         if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) {
   1099             return;
   1100         }
   1101         if (mTargetExcludes != null && mTargetExcludes.contains(view)) {
   1102             return;
   1103         }
   1104         if (mTargetTypeExcludes != null && view != null) {
   1105             int numTypes = mTargetTypeExcludes.size();
   1106             for (int i = 0; i < numTypes; ++i) {
   1107                 if (mTargetTypeExcludes.get(i).isInstance(view)) {
   1108                     return;
   1109                 }
   1110             }
   1111         }
   1112         TransitionValues values = new TransitionValues();
   1113         values.view = view;
   1114         if (start) {
   1115             captureStartValues(values);
   1116         } else {
   1117             captureEndValues(values);
   1118         }
   1119         if (start) {
   1120             if (!isListViewItem) {
   1121                 mStartValues.viewValues.put(view, values);
   1122                 if (id >= 0) {
   1123                     mStartValues.idValues.put((int) id, values);
   1124                 }
   1125             } else {
   1126                 mStartValues.itemIdValues.put(itemId, values);
   1127             }
   1128         } else {
   1129             if (!isListViewItem) {
   1130                 mEndValues.viewValues.put(view, values);
   1131                 if (id >= 0) {
   1132                     mEndValues.idValues.put((int) id, values);
   1133                 }
   1134             } else {
   1135                 mEndValues.itemIdValues.put(itemId, values);
   1136             }
   1137         }
   1138         if (view instanceof ViewGroup) {
   1139             // Don't traverse child hierarchy if there are any child-excludes on this view
   1140             if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) {
   1141                 return;
   1142             }
   1143             if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) {
   1144                 return;
   1145             }
   1146             if (mTargetTypeChildExcludes != null && view != null) {
   1147                 int numTypes = mTargetTypeChildExcludes.size();
   1148                 for (int i = 0; i < numTypes; ++i) {
   1149                     if (mTargetTypeChildExcludes.get(i).isInstance(view)) {
   1150                         return;
   1151                     }
   1152                 }
   1153             }
   1154             ViewGroup parent = (ViewGroup) view;
   1155             for (int i = 0; i < parent.getChildCount(); ++i) {
   1156                 captureHierarchy(parent.getChildAt(i), start);
   1157             }
   1158         }
   1159     }
   1160 
   1161     /**
   1162      * This method can be called by transitions to get the TransitionValues for
   1163      * any particular view during the transition-playing process. This might be
   1164      * necessary, for example, to query the before/after state of related views
   1165      * for a given transition.
   1166      */
   1167     public TransitionValues getTransitionValues(View view, boolean start) {
   1168         if (mParent != null) {
   1169             return mParent.getTransitionValues(view, start);
   1170         }
   1171         TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
   1172         TransitionValues values = valuesMaps.viewValues.get(view);
   1173         if (values == null) {
   1174             int id = view.getId();
   1175             if (id >= 0) {
   1176                 values = valuesMaps.idValues.get(id);
   1177             }
   1178             if (values == null && view.getParent() instanceof ListView) {
   1179                 ListView listview = (ListView) view.getParent();
   1180                 int position = listview.getPositionForView(view);
   1181                 long itemId = listview.getItemIdAtPosition(position);
   1182                 values = valuesMaps.itemIdValues.get(itemId);
   1183             }
   1184             // TODO: Doesn't handle the case where a view was parented to a
   1185             // ListView (with an itemId), but no longer is
   1186         }
   1187         return values;
   1188     }
   1189 
   1190     /**
   1191      * Pauses this transition, sending out calls to {@link
   1192      * TransitionListener#onTransitionPause(Transition)} to all listeners
   1193      * and pausing all running animators started by this transition.
   1194      *
   1195      * @hide
   1196      */
   1197     public void pause() {
   1198         if (!mEnded) {
   1199             ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
   1200             int numOldAnims = runningAnimators.size();
   1201             for (int i = numOldAnims - 1; i >= 0; i--) {
   1202                 Animator anim = runningAnimators.keyAt(i);
   1203                 anim.pause();
   1204             }
   1205             if (mListeners != null && mListeners.size() > 0) {
   1206                 ArrayList<TransitionListener> tmpListeners =
   1207                         (ArrayList<TransitionListener>) mListeners.clone();
   1208                 int numListeners = tmpListeners.size();
   1209                 for (int i = 0; i < numListeners; ++i) {
   1210                     tmpListeners.get(i).onTransitionPause(this);
   1211                 }
   1212             }
   1213             mPaused = true;
   1214         }
   1215     }
   1216 
   1217     /**
   1218      * Resumes this transition, sending out calls to {@link
   1219      * TransitionListener#onTransitionPause(Transition)} to all listeners
   1220      * and pausing all running animators started by this transition.
   1221      *
   1222      * @hide
   1223      */
   1224     public void resume() {
   1225         if (mPaused) {
   1226             if (!mEnded) {
   1227                 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
   1228                 int numOldAnims = runningAnimators.size();
   1229                 for (int i = numOldAnims - 1; i >= 0; i--) {
   1230                     Animator anim = runningAnimators.keyAt(i);
   1231                     anim.resume();
   1232                 }
   1233                 if (mListeners != null && mListeners.size() > 0) {
   1234                     ArrayList<TransitionListener> tmpListeners =
   1235                             (ArrayList<TransitionListener>) mListeners.clone();
   1236                     int numListeners = tmpListeners.size();
   1237                     for (int i = 0; i < numListeners; ++i) {
   1238                         tmpListeners.get(i).onTransitionResume(this);
   1239                     }
   1240                 }
   1241             }
   1242             mPaused = false;
   1243         }
   1244     }
   1245 
   1246     /**
   1247      * Called by TransitionManager to play the transition. This calls
   1248      * createAnimators() to set things up and create all of the animations and then
   1249      * runAnimations() to actually start the animations.
   1250      */
   1251     void playTransition(ViewGroup sceneRoot) {
   1252         ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
   1253         int numOldAnims = runningAnimators.size();
   1254         for (int i = numOldAnims - 1; i >= 0; i--) {
   1255             Animator anim = runningAnimators.keyAt(i);
   1256             if (anim != null) {
   1257                 AnimationInfo oldInfo = runningAnimators.get(anim);
   1258                 if (oldInfo != null && oldInfo.view != null &&
   1259                         oldInfo.view.getContext() == sceneRoot.getContext()) {
   1260                     boolean cancel = false;
   1261                     TransitionValues oldValues = oldInfo.values;
   1262                     View oldView = oldInfo.view;
   1263                     TransitionValues newValues = mEndValues.viewValues != null ?
   1264                             mEndValues.viewValues.get(oldView) : null;
   1265                     if (newValues == null) {
   1266                         newValues = mEndValues.idValues.get(oldView.getId());
   1267                     }
   1268                     if (oldValues != null) {
   1269                         // if oldValues null, then transition didn't care to stash values,
   1270                         // and won't get canceled
   1271                         if (newValues != null) {
   1272                             for (String key : oldValues.values.keySet()) {
   1273                                 Object oldValue = oldValues.values.get(key);
   1274                                 Object newValue = newValues.values.get(key);
   1275                                 if (oldValue != null && newValue != null &&
   1276                                         !oldValue.equals(newValue)) {
   1277                                     cancel = true;
   1278                                     if (DBG) {
   1279                                         Log.d(LOG_TAG, "Transition.playTransition: " +
   1280                                                 "oldValue != newValue for " + key +
   1281                                                 ": old, new = " + oldValue + ", " + newValue);
   1282                                     }
   1283                                     break;
   1284                                 }
   1285                             }
   1286                         }
   1287                     }
   1288                     if (cancel) {
   1289                         if (anim.isRunning() || anim.isStarted()) {
   1290                             if (DBG) {
   1291                                 Log.d(LOG_TAG, "Canceling anim " + anim);
   1292                             }
   1293                             anim.cancel();
   1294                         } else {
   1295                             if (DBG) {
   1296                                 Log.d(LOG_TAG, "removing anim from info list: " + anim);
   1297                             }
   1298                             runningAnimators.remove(anim);
   1299                         }
   1300                     }
   1301                 }
   1302             }
   1303         }
   1304 
   1305         createAnimators(sceneRoot, mStartValues, mEndValues);
   1306         runAnimators();
   1307     }
   1308 
   1309     /**
   1310      * This is a utility method used by subclasses to handle standard parts of
   1311      * setting up and running an Animator: it sets the {@link #getDuration()
   1312      * duration} and the {@link #getStartDelay() startDelay}, starts the
   1313      * animation, and, when the animator ends, calls {@link #end()}.
   1314      *
   1315      * @param animator The Animator to be run during this transition.
   1316      *
   1317      * @hide
   1318      */
   1319     protected void animate(Animator animator) {
   1320         // TODO: maybe pass auto-end as a boolean parameter?
   1321         if (animator == null) {
   1322             end();
   1323         } else {
   1324             if (getDuration() >= 0) {
   1325                 animator.setDuration(getDuration());
   1326             }
   1327             if (getStartDelay() >= 0) {
   1328                 animator.setStartDelay(getStartDelay());
   1329             }
   1330             if (getInterpolator() != null) {
   1331                 animator.setInterpolator(getInterpolator());
   1332             }
   1333             animator.addListener(new AnimatorListenerAdapter() {
   1334                 @Override
   1335                 public void onAnimationEnd(Animator animation) {
   1336                     end();
   1337                     animation.removeListener(this);
   1338                 }
   1339             });
   1340             animator.start();
   1341         }
   1342     }
   1343 
   1344     /**
   1345      * This method is called automatically by the transition and
   1346      * TransitionSet classes prior to a Transition subclass starting;
   1347      * subclasses should not need to call it directly.
   1348      *
   1349      * @hide
   1350      */
   1351     protected void start() {
   1352         if (mNumInstances == 0) {
   1353             if (mListeners != null && mListeners.size() > 0) {
   1354                 ArrayList<TransitionListener> tmpListeners =
   1355                         (ArrayList<TransitionListener>) mListeners.clone();
   1356                 int numListeners = tmpListeners.size();
   1357                 for (int i = 0; i < numListeners; ++i) {
   1358                     tmpListeners.get(i).onTransitionStart(this);
   1359                 }
   1360             }
   1361             mEnded = false;
   1362         }
   1363         mNumInstances++;
   1364     }
   1365 
   1366     /**
   1367      * This method is called automatically by the Transition and
   1368      * TransitionSet classes when a transition finishes, either because
   1369      * a transition did nothing (returned a null Animator from
   1370      * {@link Transition#createAnimator(ViewGroup, TransitionValues,
   1371      * TransitionValues)}) or because the transition returned a valid
   1372      * Animator and end() was called in the onAnimationEnd()
   1373      * callback of the AnimatorListener.
   1374      *
   1375      * @hide
   1376      */
   1377     protected void end() {
   1378         --mNumInstances;
   1379         if (mNumInstances == 0) {
   1380             if (mListeners != null && mListeners.size() > 0) {
   1381                 ArrayList<TransitionListener> tmpListeners =
   1382                         (ArrayList<TransitionListener>) mListeners.clone();
   1383                 int numListeners = tmpListeners.size();
   1384                 for (int i = 0; i < numListeners; ++i) {
   1385                     tmpListeners.get(i).onTransitionEnd(this);
   1386                 }
   1387             }
   1388             for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) {
   1389                 TransitionValues tv = mStartValues.itemIdValues.valueAt(i);
   1390                 View v = tv.view;
   1391                 if (v.hasTransientState()) {
   1392                     v.setHasTransientState(false);
   1393                 }
   1394             }
   1395             for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) {
   1396                 TransitionValues tv = mEndValues.itemIdValues.valueAt(i);
   1397                 View v = tv.view;
   1398                 if (v.hasTransientState()) {
   1399                     v.setHasTransientState(false);
   1400                 }
   1401             }
   1402             mEnded = true;
   1403         }
   1404     }
   1405 
   1406     /**
   1407      * This method cancels a transition that is currently running.
   1408      *
   1409      * @hide
   1410      */
   1411     protected void cancel() {
   1412         int numAnimators = mCurrentAnimators.size();
   1413         for (int i = numAnimators - 1; i >= 0; i--) {
   1414             Animator animator = mCurrentAnimators.get(i);
   1415             animator.cancel();
   1416         }
   1417         if (mListeners != null && mListeners.size() > 0) {
   1418             ArrayList<TransitionListener> tmpListeners =
   1419                     (ArrayList<TransitionListener>) mListeners.clone();
   1420             int numListeners = tmpListeners.size();
   1421             for (int i = 0; i < numListeners; ++i) {
   1422                 tmpListeners.get(i).onTransitionCancel(this);
   1423             }
   1424         }
   1425     }
   1426 
   1427     /**
   1428      * Adds a listener to the set of listeners that are sent events through the
   1429      * life of an animation, such as start, repeat, and end.
   1430      *
   1431      * @param listener the listener to be added to the current set of listeners
   1432      * for this animation.
   1433      * @return This transition object.
   1434      */
   1435     public Transition addListener(TransitionListener listener) {
   1436         if (mListeners == null) {
   1437             mListeners = new ArrayList<TransitionListener>();
   1438         }
   1439         mListeners.add(listener);
   1440         return this;
   1441     }
   1442 
   1443     /**
   1444      * Removes a listener from the set listening to this animation.
   1445      *
   1446      * @param listener the listener to be removed from the current set of
   1447      * listeners for this transition.
   1448      * @return This transition object.
   1449      */
   1450     public Transition removeListener(TransitionListener listener) {
   1451         if (mListeners == null) {
   1452             return this;
   1453         }
   1454         mListeners.remove(listener);
   1455         if (mListeners.size() == 0) {
   1456             mListeners = null;
   1457         }
   1458         return this;
   1459     }
   1460 
   1461     Transition setSceneRoot(ViewGroup sceneRoot) {
   1462         mSceneRoot = sceneRoot;
   1463         return this;
   1464     }
   1465 
   1466     void setCanRemoveViews(boolean canRemoveViews) {
   1467         mCanRemoveViews = canRemoveViews;
   1468     }
   1469 
   1470     @Override
   1471     public String toString() {
   1472         return toString("");
   1473     }
   1474 
   1475     @Override
   1476     public Transition clone() {
   1477         Transition clone = null;
   1478         try {
   1479             clone = (Transition) super.clone();
   1480             clone.mAnimators = new ArrayList<Animator>();
   1481             clone.mStartValues = new TransitionValuesMaps();
   1482             clone.mEndValues = new TransitionValuesMaps();
   1483         } catch (CloneNotSupportedException e) {}
   1484 
   1485         return clone;
   1486     }
   1487 
   1488     /**
   1489      * Returns the name of this Transition. This name is used internally to distinguish
   1490      * between different transitions to determine when interrupting transitions overlap.
   1491      * For example, a ChangeBounds running on the same target view as another ChangeBounds
   1492      * should determine whether the old transition is animating to different end values
   1493      * and should be canceled in favor of the new transition.
   1494      *
   1495      * <p>By default, a Transition's name is simply the value of {@link Class#getName()},
   1496      * but subclasses are free to override and return something different.</p>
   1497      *
   1498      * @return The name of this transition.
   1499      */
   1500     public String getName() {
   1501         return mName;
   1502     }
   1503 
   1504     String toString(String indent) {
   1505         String result = indent + getClass().getSimpleName() + "@" +
   1506                 Integer.toHexString(hashCode()) + ": ";
   1507         if (mDuration != -1) {
   1508             result += "dur(" + mDuration + ") ";
   1509         }
   1510         if (mStartDelay != -1) {
   1511             result += "dly(" + mStartDelay + ") ";
   1512         }
   1513         if (mInterpolator != null) {
   1514             result += "interp(" + mInterpolator + ") ";
   1515         }
   1516         if (mTargetIds.size() > 0 || mTargets.size() > 0) {
   1517             result += "tgts(";
   1518             if (mTargetIds.size() > 0) {
   1519                 for (int i = 0; i < mTargetIds.size(); ++i) {
   1520                     if (i > 0) {
   1521                         result += ", ";
   1522                     }
   1523                     result += mTargetIds.get(i);
   1524                 }
   1525             }
   1526             if (mTargets.size() > 0) {
   1527                 for (int i = 0; i < mTargets.size(); ++i) {
   1528                     if (i > 0) {
   1529                         result += ", ";
   1530                     }
   1531                     result += mTargets.get(i);
   1532                 }
   1533             }
   1534             result += ")";
   1535         }
   1536         return result;
   1537     }
   1538 
   1539     /**
   1540      * A transition listener receives notifications from a transition.
   1541      * Notifications indicate transition lifecycle events.
   1542      */
   1543     public static interface TransitionListener {
   1544         /**
   1545          * Notification about the start of the transition.
   1546          *
   1547          * @param transition The started transition.
   1548          */
   1549         void onTransitionStart(Transition transition);
   1550 
   1551         /**
   1552          * Notification about the end of the transition. Canceled transitions
   1553          * will always notify listeners of both the cancellation and end
   1554          * events. That is, {@link #onTransitionEnd(Transition)} is always called,
   1555          * regardless of whether the transition was canceled or played
   1556          * through to completion.
   1557          *
   1558          * @param transition The transition which reached its end.
   1559          */
   1560         void onTransitionEnd(Transition transition);
   1561 
   1562         /**
   1563          * Notification about the cancellation of the transition.
   1564          * Note that cancel may be called by a parent {@link TransitionSet} on
   1565          * a child transition which has not yet started. This allows the child
   1566          * transition to restore state on target objects which was set at
   1567          * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
   1568          * createAnimator()} time.
   1569          *
   1570          * @param transition The transition which was canceled.
   1571          */
   1572         void onTransitionCancel(Transition transition);
   1573 
   1574         /**
   1575          * Notification when a transition is paused.
   1576          * Note that createAnimator() may be called by a parent {@link TransitionSet} on
   1577          * a child transition which has not yet started. This allows the child
   1578          * transition to restore state on target objects which was set at
   1579          * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
   1580          * createAnimator()} time.
   1581          *
   1582          * @param transition The transition which was paused.
   1583          */
   1584         void onTransitionPause(Transition transition);
   1585 
   1586         /**
   1587          * Notification when a transition is resumed.
   1588          * Note that resume() may be called by a parent {@link TransitionSet} on
   1589          * a child transition which has not yet started. This allows the child
   1590          * transition to restore state which may have changed in an earlier call
   1591          * to {@link #onTransitionPause(Transition)}.
   1592          *
   1593          * @param transition The transition which was resumed.
   1594          */
   1595         void onTransitionResume(Transition transition);
   1596     }
   1597 
   1598     /**
   1599      * Utility adapter class to avoid having to override all three methods
   1600      * whenever someone just wants to listen for a single event.
   1601      *
   1602      * @hide
   1603      * */
   1604     public static class TransitionListenerAdapter implements TransitionListener {
   1605         @Override
   1606         public void onTransitionStart(Transition transition) {
   1607         }
   1608 
   1609         @Override
   1610         public void onTransitionEnd(Transition transition) {
   1611         }
   1612 
   1613         @Override
   1614         public void onTransitionCancel(Transition transition) {
   1615         }
   1616 
   1617         @Override
   1618         public void onTransitionPause(Transition transition) {
   1619         }
   1620 
   1621         @Override
   1622         public void onTransitionResume(Transition transition) {
   1623         }
   1624     }
   1625 
   1626     /**
   1627      * Holds information about each animator used when a new transition starts
   1628      * while other transitions are still running to determine whether a running
   1629      * animation should be canceled or a new animation noop'd. The structure holds
   1630      * information about the state that an animation is going to, to be compared to
   1631      * end state of a new animation.
   1632      */
   1633     private static class AnimationInfo {
   1634         View view;
   1635         String name;
   1636         TransitionValues values;
   1637 
   1638         AnimationInfo(View view, String name, TransitionValues values) {
   1639             this.view = view;
   1640             this.name = name;
   1641             this.values = values;
   1642         }
   1643     }
   1644 
   1645     /**
   1646      * Utility class for managing typed ArrayLists efficiently. In particular, this
   1647      * can be useful for lists that we don't expect to be used often (eg, the exclude
   1648      * lists), so we'd like to keep them nulled out by default. This causes the code to
   1649      * become tedious, with constant null checks, code to allocate when necessary,
   1650      * and code to null out the reference when the list is empty. This class encapsulates
   1651      * all of that functionality into simple add()/remove() methods which perform the
   1652      * necessary checks, allocation/null-out as appropriate, and return the
   1653      * resulting list.
   1654      */
   1655     private static class ArrayListManager {
   1656 
   1657         /**
   1658          * Add the specified item to the list, returning the resulting list.
   1659          * The returned list can either the be same list passed in or, if that
   1660          * list was null, the new list that was created.
   1661          *
   1662          * Note that the list holds unique items; if the item already exists in the
   1663          * list, the list is not modified.
   1664          */
   1665         static <T> ArrayList<T> add(ArrayList<T> list, T item) {
   1666             if (list == null) {
   1667                 list = new ArrayList<T>();
   1668             }
   1669             if (!list.contains(item)) {
   1670                 list.add(item);
   1671             }
   1672             return list;
   1673         }
   1674 
   1675         /**
   1676          * Remove the specified item from the list, returning the resulting list.
   1677          * The returned list can either the be same list passed in or, if that
   1678          * list becomes empty as a result of the remove(), the new list was created.
   1679          */
   1680         static <T> ArrayList<T> remove(ArrayList<T> list, T item) {
   1681             if (list != null) {
   1682                 list.remove(item);
   1683                 if (list.isEmpty()) {
   1684                     list = null;
   1685                 }
   1686             }
   1687             return list;
   1688         }
   1689     }
   1690 
   1691 }
   1692