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