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