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