Transition.java revision a98fb7ab6a17d27395cf2c8e86060af49b861be6
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 =
355                new SparseArray<TransitionValues>(endValues.idValues.size());
356        for (int i = 0; i < endValues.idValues.size(); ++i) {
357            int id = endValues.idValues.keyAt(i);
358            endIdCopy.put(id, endValues.idValues.valueAt(i));
359        }
360        LongSparseArray<TransitionValues> endItemIdCopy =
361                new LongSparseArray<TransitionValues>(endValues.itemIdValues.size());
362        for (int i = 0; i < endValues.itemIdValues.size(); ++i) {
363            long id = endValues.itemIdValues.keyAt(i);
364            endItemIdCopy.put(id, endValues.itemIdValues.valueAt(i));
365        }
366        // Walk through the start values, playing everything we find
367        // Remove from the end set as we go
368        ArrayList<TransitionValues> startValuesList = new ArrayList<TransitionValues>();
369        ArrayList<TransitionValues> endValuesList = new ArrayList<TransitionValues>();
370        for (View view : startValues.viewValues.keySet()) {
371            TransitionValues start = null;
372            TransitionValues end = null;
373            boolean isInListView = false;
374            if (view.getParent() instanceof ListView) {
375                isInListView = true;
376            }
377            if (!isInListView) {
378                int id = view.getId();
379                start = startValues.viewValues.get(view) != null ?
380                        startValues.viewValues.get(view) : startValues.idValues.get(id);
381                if (endValues.viewValues.get(view) != null) {
382                    end = endValues.viewValues.get(view);
383                    endCopy.remove(view);
384                } else if (id != View.NO_ID) {
385                    end = endValues.idValues.get(id);
386                    View removeView = null;
387                    for (View viewToRemove : endCopy.keySet()) {
388                        if (viewToRemove.getId() == id) {
389                            removeView = viewToRemove;
390                        }
391                    }
392                    if (removeView != null) {
393                        endCopy.remove(removeView);
394                    }
395                }
396                endIdCopy.remove(id);
397                if (isValidTarget(view, id)) {
398                    startValuesList.add(start);
399                    endValuesList.add(end);
400                }
401            } else {
402                ListView parent = (ListView) view.getParent();
403                if (parent.getAdapter().hasStableIds()) {
404                    int position = parent.getPositionForView(view);
405                    long itemId = parent.getItemIdAtPosition(position);
406                    start = startValues.itemIdValues.get(itemId);
407                    endItemIdCopy.remove(itemId);
408                    // TODO: deal with targetIDs for itemIDs for ListView items
409                    startValuesList.add(start);
410                    endValuesList.add(end);
411                }
412            }
413        }
414        int startItemIdCopySize = startValues.itemIdValues.size();
415        for (int i = 0; i < startItemIdCopySize; ++i) {
416            long id = startValues.itemIdValues.keyAt(i);
417            if (isValidTarget(null, id)) {
418                TransitionValues start = startValues.itemIdValues.get(id);
419                TransitionValues end = endValues.itemIdValues.get(id);
420                endItemIdCopy.remove(id);
421                startValuesList.add(start);
422                endValuesList.add(end);
423            }
424        }
425        // Now walk through the remains of the end set
426        for (View view : endCopy.keySet()) {
427            int id = view.getId();
428            if (isValidTarget(view, id)) {
429                TransitionValues start = startValues.viewValues.get(view) != null ?
430                        startValues.viewValues.get(view) : startValues.idValues.get(id);
431                TransitionValues end = endCopy.get(view);
432                endIdCopy.remove(id);
433                startValuesList.add(start);
434                endValuesList.add(end);
435            }
436        }
437        int endIdCopySize = endIdCopy.size();
438        for (int i = 0; i < endIdCopySize; ++i) {
439            int id = endIdCopy.keyAt(i);
440            if (isValidTarget(null, id)) {
441                TransitionValues start = startValues.idValues.get(id);
442                TransitionValues end = endIdCopy.get(id);
443                startValuesList.add(start);
444                endValuesList.add(end);
445            }
446        }
447        int endItemIdCopySize = endItemIdCopy.size();
448        for (int i = 0; i < endItemIdCopySize; ++i) {
449            long id = endItemIdCopy.keyAt(i);
450            // TODO: Deal with targetIDs and itemIDs
451            TransitionValues start = startValues.itemIdValues.get(id);
452            TransitionValues end = endItemIdCopy.get(id);
453            startValuesList.add(start);
454            endValuesList.add(end);
455        }
456        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
457        long minStartDelay = Long.MAX_VALUE;
458        int minAnimator = mAnimators.size();
459        SparseLongArray startDelays = new SparseLongArray();
460        for (int i = 0; i < startValuesList.size(); ++i) {
461            TransitionValues start = startValuesList.get(i);
462            TransitionValues end = endValuesList.get(i);
463            // Only bother trying to animate with values that differ between start/end
464            if (start != null || end != null) {
465                if (start == null || !start.equals(end)) {
466                    if (DBG) {
467                        View view = (end != null) ? end.view : start.view;
468                        Log.d(LOG_TAG, "  differing start/end values for view " +
469                                view);
470                        if (start == null || end == null) {
471                            Log.d(LOG_TAG, "    " + ((start == null) ?
472                                    "start null, end non-null" : "start non-null, end null"));
473                        } else {
474                            for (String key : start.values.keySet()) {
475                                Object startValue = start.values.get(key);
476                                Object endValue = end.values.get(key);
477                                if (startValue != endValue && !startValue.equals(endValue)) {
478                                    Log.d(LOG_TAG, "    " + key + ": start(" + startValue +
479                                            "), end(" + endValue +")");
480                                }
481                            }
482                        }
483                    }
484                    // TODO: what to do about targetIds and itemIds?
485                    Animator animator = createAnimator(sceneRoot, start, end);
486                    if (animator != null) {
487                        // Save animation info for future cancellation purposes
488                        View view = null;
489                        TransitionValues infoValues = null;
490                        if (end != null) {
491                            view = end.view;
492                            String[] properties = getTransitionProperties();
493                            if (view != null && properties != null && properties.length > 0) {
494                                infoValues = new TransitionValues();
495                                infoValues.view = view;
496                                TransitionValues newValues = endValues.viewValues.get(view);
497                                if (newValues != null) {
498                                    for (int j = 0; j < properties.length; ++j) {
499                                        infoValues.values.put(properties[j],
500                                                newValues.values.get(properties[j]));
501                                    }
502                                }
503                                int numExistingAnims = runningAnimators.size();
504                                for (int j = 0; j < numExistingAnims; ++j) {
505                                    Animator anim = runningAnimators.keyAt(j);
506                                    AnimationInfo info = runningAnimators.get(anim);
507                                    if (info.values != null && info.view == view &&
508                                            ((info.name == null && getName() == null) ||
509                                            info.name.equals(getName()))) {
510                                        if (info.values.equals(infoValues)) {
511                                            // Favor the old animator
512                                            animator = null;
513                                            break;
514                                        }
515                                    }
516                                }
517                            }
518                        } else {
519                            view = (start != null) ? start.view : null;
520                        }
521                        if (animator != null) {
522                            if (mPropagation != null) {
523                                long delay = mPropagation
524                                        .getStartDelay(sceneRoot, this, start, end);
525                                startDelays.put(mAnimators.size(), delay);
526                                minStartDelay = Math.min(delay, minStartDelay);
527                            }
528                            AnimationInfo info = new AnimationInfo(view, getName(),
529                                    sceneRoot.getWindowId(), infoValues);
530                            runningAnimators.put(animator, info);
531                            mAnimators.add(animator);
532                        }
533                    }
534                }
535            }
536        }
537        if (minStartDelay != 0) {
538            for (int i = 0; i < startDelays.size(); i++) {
539                int index = startDelays.keyAt(i);
540                Animator animator = mAnimators.get(index);
541                long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
542                animator.setStartDelay(delay);
543            }
544        }
545    }
546
547    /**
548     * Internal utility method for checking whether a given view/id
549     * is valid for this transition, where "valid" means that either
550     * the Transition has no target/targetId list (the default, in which
551     * cause the transition should act on all views in the hiearchy), or
552     * the given view is in the target list or the view id is in the
553     * targetId list. If the target parameter is null, then the target list
554     * is not checked (this is in the case of ListView items, where the
555     * views are ignored and only the ids are used).
556     */
557    boolean isValidTarget(View target, long targetId) {
558        if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) {
559            return false;
560        }
561        if (mTargetExcludes != null && mTargetExcludes.contains(target)) {
562            return false;
563        }
564        if (mTargetTypeExcludes != null && target != null) {
565            int numTypes = mTargetTypeExcludes.size();
566            for (int i = 0; i < numTypes; ++i) {
567                Class type = mTargetTypeExcludes.get(i);
568                if (type.isInstance(target)) {
569                    return false;
570                }
571            }
572        }
573        if (mTargetIds.size() == 0 && mTargets.size() == 0 && mTargetTypes == null) {
574            return true;
575        }
576        if (mTargetIds.contains((int) targetId) || mTargets.contains(target)) {
577            return true;
578        }
579        if (mTargetTypes != null) {
580            for (int i = 0; i < mTargetTypes.size(); ++i) {
581                if (mTargetTypes.get(i).isInstance(target)) {
582                    return true;
583                }
584            }
585        }
586        return false;
587    }
588
589    private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() {
590        ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get();
591        if (runningAnimators == null) {
592            runningAnimators = new ArrayMap<Animator, AnimationInfo>();
593            sRunningAnimators.set(runningAnimators);
594        }
595        return runningAnimators;
596    }
597
598    /**
599     * This is called internally once all animations have been set up by the
600     * transition hierarchy.
601     *
602     * @hide
603     */
604    protected void runAnimators() {
605        if (DBG) {
606            Log.d(LOG_TAG, "runAnimators() on " + this);
607        }
608        start();
609        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
610        // Now start every Animator that was previously created for this transition
611        for (Animator anim : mAnimators) {
612            if (DBG) {
613                Log.d(LOG_TAG, "  anim: " + anim);
614            }
615            if (runningAnimators.containsKey(anim)) {
616                start();
617                runAnimator(anim, runningAnimators);
618            }
619        }
620        mAnimators.clear();
621        end();
622    }
623
624    private void runAnimator(Animator animator,
625            final ArrayMap<Animator, AnimationInfo> runningAnimators) {
626        if (animator != null) {
627            // TODO: could be a single listener instance for all of them since it uses the param
628            animator.addListener(new AnimatorListenerAdapter() {
629                @Override
630                public void onAnimationStart(Animator animation) {
631                    mCurrentAnimators.add(animation);
632                }
633                @Override
634                public void onAnimationEnd(Animator animation) {
635                    runningAnimators.remove(animation);
636                    mCurrentAnimators.remove(animation);
637                }
638            });
639            animate(animator);
640        }
641    }
642
643    /**
644     * Captures the values in the start scene for the properties that this
645     * transition monitors. These values are then passed as the startValues
646     * structure in a later call to
647     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
648     * The main concern for an implementation is what the
649     * properties are that the transition cares about and what the values are
650     * for all of those properties. The start and end values will be compared
651     * later during the
652     * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
653     * method to determine what, if any, animations, should be run.
654     *
655     * <p>Subclasses must implement this method. The method should only be called by the
656     * transition system; it is not intended to be called from external classes.</p>
657     *
658     * @param transitionValues The holder for any values that the Transition
659     * wishes to store. Values are stored in the <code>values</code> field
660     * of this TransitionValues object and are keyed from
661     * a String value. For example, to store a view's rotation value,
662     * a transition might call
663     * <code>transitionValues.values.put("appname:transitionname:rotation",
664     * view.getRotation())</code>. The target view will already be stored in
665     * the transitionValues structure when this method is called.
666     *
667     * @see #captureEndValues(TransitionValues)
668     * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
669     */
670    public abstract void captureStartValues(TransitionValues transitionValues);
671
672    /**
673     * Captures the values in the end scene for the properties that this
674     * transition monitors. These values are then passed as the endValues
675     * structure in a later call to
676     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
677     * The main concern for an implementation is what the
678     * properties are that the transition cares about and what the values are
679     * for all of those properties. The start and end values will be compared
680     * later during the
681     * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
682     * method to determine what, if any, animations, should be run.
683     *
684     * <p>Subclasses must implement this method. The method should only be called by the
685     * transition system; it is not intended to be called from external classes.</p>
686     *
687     * @param transitionValues The holder for any values that the Transition
688     * wishes to store. Values are stored in the <code>values</code> field
689     * of this TransitionValues object and are keyed from
690     * a String value. For example, to store a view's rotation value,
691     * a transition might call
692     * <code>transitionValues.values.put("appname:transitionname:rotation",
693     * view.getRotation())</code>. The target view will already be stored in
694     * the transitionValues structure when this method is called.
695     *
696     * @see #captureStartValues(TransitionValues)
697     * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
698     */
699    public abstract void captureEndValues(TransitionValues transitionValues);
700
701    /**
702     * Adds the id of a target view that this Transition is interested in
703     * animating. By default, there are no targetIds, and a Transition will
704     * listen for changes on every view in the hierarchy below the sceneRoot
705     * of the Scene being transitioned into. Setting targetIds constrains
706     * the Transition to only listen for, and act on, views with these IDs.
707     * Views with different IDs, or no IDs whatsoever, will be ignored.
708     *
709     * <p>Note that using ids to specify targets implies that ids should be unique
710     * within the view hierarchy underneat the scene root.</p>
711     *
712     * @see View#getId()
713     * @param targetId The id of a target view, must be a positive number.
714     * @return The Transition to which the targetId is added.
715     * Returning the same object makes it easier to chain calls during
716     * construction, such as
717     * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code>
718     */
719    public Transition addTarget(int targetId) {
720        if (targetId > 0) {
721            mTargetIds.add(targetId);
722        }
723        return this;
724    }
725
726    /**
727     * Adds the Class of a target view that this Transition is interested in
728     * animating. By default, there are no targetTypes, and a Transition will
729     * listen for changes on every view in the hierarchy below the sceneRoot
730     * of the Scene being transitioned into. Setting targetTypes constrains
731     * the Transition to only listen for, and act on, views with these classes.
732     * Views with different classes will be ignored.
733     *
734     * <p>Note that any View that can be cast to targetType will be included, so
735     * if targetType is <code>View.class</code>, all Views will be included.</p>
736     *
737     * @see #addTarget(int)
738     * @see #addTarget(android.view.View)
739     * @see #excludeTarget(Class, boolean)
740     * @see #excludeChildren(Class, boolean)
741     *
742     * @param targetType The type to include when running this transition.
743     * @return The Transition to which the target class was added.
744     * Returning the same object makes it easier to chain calls during
745     * construction, such as
746     * <code>transitionSet.addTransitions(new Fade()).addTarget(ImageView.class);</code>
747     */
748    public Transition addTarget(Class targetType) {
749        if (mTargetTypes == null) {
750            mTargetTypes = new ArrayList<Class>();
751        }
752        mTargetTypes.add(targetType);
753        return this;
754    }
755
756    /**
757     * Removes the given targetId from the list of ids that this Transition
758     * is interested in animating.
759     *
760     * @param targetId The id of a target view, must be a positive number.
761     * @return The Transition from which the targetId is removed.
762     * Returning the same object makes it easier to chain calls during
763     * construction, such as
764     * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code>
765     */
766    public Transition removeTarget(int targetId) {
767        if (targetId > 0) {
768            mTargetIds.remove(targetId);
769        }
770        return this;
771    }
772
773    /**
774     * Whether to add the given id to the list of target ids to exclude from this
775     * transition. The <code>exclude</code> parameter specifies whether the target
776     * should be added to or removed from the excluded list.
777     *
778     * <p>Excluding targets is a general mechanism for allowing transitions to run on
779     * a view hierarchy while skipping target views that should not be part of
780     * the transition. For example, you may want to avoid animating children
781     * of a specific ListView or Spinner. Views can be excluded either by their
782     * id, or by their instance reference, or by the Class of that view
783     * (eg, {@link Spinner}).</p>
784     *
785     * @see #excludeChildren(int, boolean)
786     * @see #excludeTarget(View, boolean)
787     * @see #excludeTarget(Class, boolean)
788     *
789     * @param targetId The id of a target to ignore when running this transition.
790     * @param exclude Whether to add the target to or remove the target from the
791     * current list of excluded targets.
792     * @return This transition object.
793     */
794    public Transition excludeTarget(int targetId, boolean exclude) {
795        mTargetIdExcludes = excludeId(mTargetIdExcludes, targetId, exclude);
796        return this;
797    }
798
799    /**
800     * Whether to add the children of the given id to the list of targets to exclude
801     * from this transition. The <code>exclude</code> parameter specifies whether
802     * the children of the target should be added to or removed from the excluded list.
803     * Excluding children in this way provides a simple mechanism for excluding all
804     * children of specific targets, rather than individually excluding each
805     * child individually.
806     *
807     * <p>Excluding targets is a general mechanism for allowing transitions to run on
808     * a view hierarchy while skipping target views that should not be part of
809     * the transition. For example, you may want to avoid animating children
810     * of a specific ListView or Spinner. Views can be excluded either by their
811     * id, or by their instance reference, or by the Class of that view
812     * (eg, {@link Spinner}).</p>
813     *
814     * @see #excludeTarget(int, boolean)
815     * @see #excludeChildren(View, boolean)
816     * @see #excludeChildren(Class, boolean)
817     *
818     * @param targetId The id of a target whose children should be ignored when running
819     * this transition.
820     * @param exclude Whether to add the target to or remove the target from the
821     * current list of excluded-child targets.
822     * @return This transition object.
823     */
824    public Transition excludeChildren(int targetId, boolean exclude) {
825        mTargetIdChildExcludes = excludeId(mTargetIdChildExcludes, targetId, exclude);
826        return this;
827    }
828
829    /**
830     * Utility method to manage the boilerplate code that is the same whether we
831     * are excluding targets or their children.
832     */
833    private ArrayList<Integer> excludeId(ArrayList<Integer> list, int targetId, boolean exclude) {
834        if (targetId > 0) {
835            if (exclude) {
836                list = ArrayListManager.add(list, targetId);
837            } else {
838                list = ArrayListManager.remove(list, targetId);
839            }
840        }
841        return list;
842    }
843
844    /**
845     * Whether to add the given target to the list of targets to exclude from this
846     * transition. The <code>exclude</code> parameter specifies whether the target
847     * should be added to or removed from the excluded list.
848     *
849     * <p>Excluding targets is a general mechanism for allowing transitions to run on
850     * a view hierarchy while skipping target views that should not be part of
851     * the transition. For example, you may want to avoid animating children
852     * of a specific ListView or Spinner. Views can be excluded either by their
853     * id, or by their instance reference, or by the Class of that view
854     * (eg, {@link Spinner}).</p>
855     *
856     * @see #excludeChildren(View, boolean)
857     * @see #excludeTarget(int, boolean)
858     * @see #excludeTarget(Class, boolean)
859     *
860     * @param target The target to ignore when running this transition.
861     * @param exclude Whether to add the target to or remove the target from the
862     * current list of excluded targets.
863     * @return This transition object.
864     */
865    public Transition excludeTarget(View target, boolean exclude) {
866        mTargetExcludes = excludeView(mTargetExcludes, target, exclude);
867        return this;
868    }
869
870    /**
871     * Whether to add the children of given target to the list of target children
872     * to exclude from this transition. The <code>exclude</code> parameter specifies
873     * whether the target should be added to or removed from the excluded list.
874     *
875     * <p>Excluding targets is a general mechanism for allowing transitions to run on
876     * a view hierarchy while skipping target views that should not be part of
877     * the transition. For example, you may want to avoid animating children
878     * of a specific ListView or Spinner. Views can be excluded either by their
879     * id, or by their instance reference, or by the Class of that view
880     * (eg, {@link Spinner}).</p>
881     *
882     * @see #excludeTarget(View, boolean)
883     * @see #excludeChildren(int, boolean)
884     * @see #excludeChildren(Class, boolean)
885     *
886     * @param target The target to ignore when running this transition.
887     * @param exclude Whether to add the target to or remove the target from the
888     * current list of excluded targets.
889     * @return This transition object.
890     */
891    public Transition excludeChildren(View target, boolean exclude) {
892        mTargetChildExcludes = excludeView(mTargetChildExcludes, target, exclude);
893        return this;
894    }
895
896    /**
897     * Utility method to manage the boilerplate code that is the same whether we
898     * are excluding targets or their children.
899     */
900    private ArrayList<View> excludeView(ArrayList<View> list, View target, boolean exclude) {
901        if (target != null) {
902            if (exclude) {
903                list = ArrayListManager.add(list, target);
904            } else {
905                list = ArrayListManager.remove(list, target);
906            }
907        }
908        return list;
909    }
910
911    /**
912     * Whether to add the given type to the list of types to exclude from this
913     * transition. The <code>exclude</code> parameter specifies whether the target
914     * type should be added to or removed from 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 #excludeChildren(Class, boolean)
924     * @see #excludeTarget(int, boolean)
925     * @see #excludeTarget(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 excludeTarget(Class type, boolean exclude) {
933        mTargetTypeExcludes = excludeType(mTargetTypeExcludes, type, exclude);
934        return this;
935    }
936
937    /**
938     * Whether to add the given type to the list of types whose children should
939     * be excluded from this transition. The <code>exclude</code> parameter
940     * specifies whether the target type should be added to or removed from
941     * the excluded list.
942     *
943     * <p>Excluding targets is a general mechanism for allowing transitions to run on
944     * a view hierarchy while skipping target views that should not be part of
945     * the transition. For example, you may want to avoid animating children
946     * of a specific ListView or Spinner. Views can be excluded either by their
947     * id, or by their instance reference, or by the Class of that view
948     * (eg, {@link Spinner}).</p>
949     *
950     * @see #excludeTarget(Class, boolean)
951     * @see #excludeChildren(int, boolean)
952     * @see #excludeChildren(View, boolean)
953     *
954     * @param type The type to ignore when running this transition.
955     * @param exclude Whether to add the target type to or remove it from the
956     * current list of excluded target types.
957     * @return This transition object.
958     */
959    public Transition excludeChildren(Class type, boolean exclude) {
960        mTargetTypeChildExcludes = excludeType(mTargetTypeChildExcludes, type, exclude);
961        return this;
962    }
963
964    /**
965     * Utility method to manage the boilerplate code that is the same whether we
966     * are excluding targets or their children.
967     */
968    private ArrayList<Class> excludeType(ArrayList<Class> list, Class type, boolean exclude) {
969        if (type != null) {
970            if (exclude) {
971                list = ArrayListManager.add(list, type);
972            } else {
973                list = ArrayListManager.remove(list, type);
974            }
975        }
976        return list;
977    }
978
979    /**
980     * Sets the target view instances that this Transition is interested in
981     * animating. By default, there are no targets, and a Transition will
982     * listen for changes on every view in the hierarchy below the sceneRoot
983     * of the Scene being transitioned into. Setting targets constrains
984     * the Transition to only listen for, and act on, these views.
985     * All other views will be ignored.
986     *
987     * <p>The target list is like the {@link #addTarget(int) targetId}
988     * list except this list specifies the actual View instances, not the ids
989     * of the views. This is an important distinction when scene changes involve
990     * view hierarchies which have been inflated separately; different views may
991     * share the same id but not actually be the same instance. If the transition
992     * should treat those views as the same, then {@link #addTarget(int)} should be used
993     * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve
994     * changes all within the same view hierarchy, among views which do not
995     * necessarily have ids set on them, then the target list of views may be more
996     * convenient.</p>
997     *
998     * @see #addTarget(int)
999     * @param target A View on which the Transition will act, must be non-null.
1000     * @return The Transition to which the target is added.
1001     * Returning the same object makes it easier to chain calls during
1002     * construction, such as
1003     * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code>
1004     */
1005    public Transition addTarget(View target) {
1006        mTargets.add(target);
1007        return this;
1008    }
1009
1010    /**
1011     * Removes the given target from the list of targets that this Transition
1012     * is interested in animating.
1013     *
1014     * @param target The target view, must be non-null.
1015     * @return Transition The Transition from which the target is removed.
1016     * Returning the same object makes it easier to chain calls during
1017     * construction, such as
1018     * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code>
1019     */
1020    public Transition removeTarget(View target) {
1021        if (target != null) {
1022            mTargets.remove(target);
1023        }
1024        return this;
1025    }
1026
1027    /**
1028     * Returns the array of target IDs that this transition limits itself to
1029     * tracking and animating. If the array is null for both this method and
1030     * {@link #getTargets()}, then this transition is
1031     * not limited to specific views, and will handle changes to any views
1032     * in the hierarchy of a scene change.
1033     *
1034     * @return the list of target IDs
1035     */
1036    public List<Integer> getTargetIds() {
1037        return mTargetIds;
1038    }
1039
1040    /**
1041     * Returns the array of target views that this transition limits itself to
1042     * tracking and animating. If the array is null for both this method and
1043     * {@link #getTargetIds()}, then this transition is
1044     * not limited to specific views, and will handle changes to any views
1045     * in the hierarchy of a scene change.
1046     *
1047     * @return the list of target views
1048     */
1049    public List<View> getTargets() {
1050        return mTargets;
1051    }
1052
1053    /**
1054     * Recursive method that captures values for the given view and the
1055     * hierarchy underneath it.
1056     * @param sceneRoot The root of the view hierarchy being captured
1057     * @param start true if this capture is happening before the scene change,
1058     * false otherwise
1059     */
1060    void captureValues(ViewGroup sceneRoot, boolean start) {
1061        clearValues(start);
1062        if (mTargetIds.size() > 0 || mTargets.size() > 0) {
1063            if (mTargetIds.size() > 0) {
1064                for (int i = 0; i < mTargetIds.size(); ++i) {
1065                    int id = mTargetIds.get(i);
1066                    View view = sceneRoot.findViewById(id);
1067                    if (view != null) {
1068                        TransitionValues values = new TransitionValues();
1069                        values.view = view;
1070                        if (start) {
1071                            captureStartValues(values);
1072                        } else {
1073                            captureEndValues(values);
1074                        }
1075                        capturePropagationValues(values);
1076                        if (start) {
1077                            mStartValues.viewValues.put(view, values);
1078                            if (id >= 0) {
1079                                mStartValues.idValues.put(id, values);
1080                            }
1081                        } else {
1082                            mEndValues.viewValues.put(view, values);
1083                            if (id >= 0) {
1084                                mEndValues.idValues.put(id, values);
1085                            }
1086                        }
1087                    }
1088                }
1089            }
1090            if (mTargets.size() > 0) {
1091                for (int i = 0; i < mTargets.size(); ++i) {
1092                    View view = mTargets.get(i);
1093                    if (view != null) {
1094                        TransitionValues values = new TransitionValues();
1095                        values.view = view;
1096                        if (start) {
1097                            captureStartValues(values);
1098                        } else {
1099                            captureEndValues(values);
1100                        }
1101                        capturePropagationValues(values);
1102                        if (start) {
1103                            mStartValues.viewValues.put(view, values);
1104                        } else {
1105                            mEndValues.viewValues.put(view, values);
1106                        }
1107                    }
1108                }
1109            }
1110        } else {
1111            captureHierarchy(sceneRoot, start);
1112        }
1113    }
1114
1115    /**
1116     * Clear valuesMaps for specified start/end state
1117     *
1118     * @param start true if the start values should be cleared, false otherwise
1119     */
1120    void clearValues(boolean start) {
1121        if (start) {
1122            mStartValues.viewValues.clear();
1123            mStartValues.idValues.clear();
1124            mStartValues.itemIdValues.clear();
1125        } else {
1126            mEndValues.viewValues.clear();
1127            mEndValues.idValues.clear();
1128            mEndValues.itemIdValues.clear();
1129        }
1130    }
1131
1132    /**
1133     * Recursive method which captures values for an entire view hierarchy,
1134     * starting at some root view. Transitions without targetIDs will use this
1135     * method to capture values for all possible views.
1136     *
1137     * @param view The view for which to capture values. Children of this View
1138     * will also be captured, recursively down to the leaf nodes.
1139     * @param start true if values are being captured in the start scene, false
1140     * otherwise.
1141     */
1142    private void captureHierarchy(View view, boolean start) {
1143        if (view == null) {
1144            return;
1145        }
1146        boolean isListViewItem = false;
1147        if (view.getParent() instanceof ListView) {
1148            isListViewItem = true;
1149        }
1150        if (isListViewItem && !((ListView) view.getParent()).getAdapter().hasStableIds()) {
1151            // ignore listview children unless we can track them with stable IDs
1152            return;
1153        }
1154        int id = View.NO_ID;
1155        long itemId = View.NO_ID;
1156        if (!isListViewItem) {
1157            id = view.getId();
1158        } else {
1159            ListView listview = (ListView) view.getParent();
1160            int position = listview.getPositionForView(view);
1161            itemId = listview.getItemIdAtPosition(position);
1162            view.setHasTransientState(true);
1163        }
1164        if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) {
1165            return;
1166        }
1167        if (mTargetExcludes != null && mTargetExcludes.contains(view)) {
1168            return;
1169        }
1170        if (mTargetTypeExcludes != null && view != null) {
1171            int numTypes = mTargetTypeExcludes.size();
1172            for (int i = 0; i < numTypes; ++i) {
1173                if (mTargetTypeExcludes.get(i).isInstance(view)) {
1174                    return;
1175                }
1176            }
1177        }
1178        if (view.getParent() instanceof ViewGroup) {
1179            TransitionValues values = new TransitionValues();
1180            values.view = view;
1181            if (start) {
1182                captureStartValues(values);
1183            } else {
1184                captureEndValues(values);
1185            }
1186            capturePropagationValues(values);
1187            if (start) {
1188                if (!isListViewItem) {
1189                    mStartValues.viewValues.put(view, values);
1190                    if (id >= 0) {
1191                        mStartValues.idValues.put((int) id, values);
1192                    }
1193                } else {
1194                    mStartValues.itemIdValues.put(itemId, values);
1195                }
1196            } else {
1197                if (!isListViewItem) {
1198                    mEndValues.viewValues.put(view, values);
1199                    if (id >= 0) {
1200                        mEndValues.idValues.put((int) id, values);
1201                    }
1202                } else {
1203                    mEndValues.itemIdValues.put(itemId, values);
1204                }
1205            }
1206        }
1207        if (view instanceof ViewGroup) {
1208            // Don't traverse child hierarchy if there are any child-excludes on this view
1209            if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) {
1210                return;
1211            }
1212            if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) {
1213                return;
1214            }
1215            if (mTargetTypeChildExcludes != null && view != null) {
1216                int numTypes = mTargetTypeChildExcludes.size();
1217                for (int i = 0; i < numTypes; ++i) {
1218                    if (mTargetTypeChildExcludes.get(i).isInstance(view)) {
1219                        return;
1220                    }
1221                }
1222            }
1223            ViewGroup parent = (ViewGroup) view;
1224            for (int i = 0; i < parent.getChildCount(); ++i) {
1225                captureHierarchy(parent.getChildAt(i), start);
1226            }
1227        }
1228    }
1229
1230    /**
1231     * This method can be called by transitions to get the TransitionValues for
1232     * any particular view during the transition-playing process. This might be
1233     * necessary, for example, to query the before/after state of related views
1234     * for a given transition.
1235     */
1236    public TransitionValues getTransitionValues(View view, boolean start) {
1237        if (mParent != null) {
1238            return mParent.getTransitionValues(view, start);
1239        }
1240        TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
1241        TransitionValues values = valuesMaps.viewValues.get(view);
1242        if (values == null) {
1243            int id = view.getId();
1244            if (id >= 0) {
1245                values = valuesMaps.idValues.get(id);
1246            }
1247            if (values == null && view.getParent() instanceof ListView) {
1248                ListView listview = (ListView) view.getParent();
1249                int position = listview.getPositionForView(view);
1250                long itemId = listview.getItemIdAtPosition(position);
1251                values = valuesMaps.itemIdValues.get(itemId);
1252            }
1253            // TODO: Doesn't handle the case where a view was parented to a
1254            // ListView (with an itemId), but no longer is
1255        }
1256        return values;
1257    }
1258
1259    /**
1260     * Pauses this transition, sending out calls to {@link
1261     * TransitionListener#onTransitionPause(Transition)} to all listeners
1262     * and pausing all running animators started by this transition.
1263     *
1264     * @hide
1265     */
1266    public void pause(View sceneRoot) {
1267        if (!mEnded) {
1268            ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1269            int numOldAnims = runningAnimators.size();
1270            WindowId windowId = sceneRoot.getWindowId();
1271            for (int i = numOldAnims - 1; i >= 0; i--) {
1272                AnimationInfo info = runningAnimators.valueAt(i);
1273                if (info.view != null && windowId.equals(info.windowId)) {
1274                    Animator anim = runningAnimators.keyAt(i);
1275                    anim.pause();
1276                }
1277            }
1278            if (mListeners != null && mListeners.size() > 0) {
1279                ArrayList<TransitionListener> tmpListeners =
1280                        (ArrayList<TransitionListener>) mListeners.clone();
1281                int numListeners = tmpListeners.size();
1282                for (int i = 0; i < numListeners; ++i) {
1283                    tmpListeners.get(i).onTransitionPause(this);
1284                }
1285            }
1286            mPaused = true;
1287        }
1288    }
1289
1290    /**
1291     * Resumes this transition, sending out calls to {@link
1292     * TransitionListener#onTransitionPause(Transition)} to all listeners
1293     * and pausing all running animators started by this transition.
1294     *
1295     * @hide
1296     */
1297    public void resume(View sceneRoot) {
1298        if (mPaused) {
1299            if (!mEnded) {
1300                ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1301                int numOldAnims = runningAnimators.size();
1302                WindowId windowId = sceneRoot.getWindowId();
1303                for (int i = numOldAnims - 1; i >= 0; i--) {
1304                    AnimationInfo info = runningAnimators.valueAt(i);
1305                    if (info.view != null && windowId.equals(info.windowId)) {
1306                        Animator anim = runningAnimators.keyAt(i);
1307                        anim.resume();
1308                    }
1309                }
1310                if (mListeners != null && mListeners.size() > 0) {
1311                    ArrayList<TransitionListener> tmpListeners =
1312                            (ArrayList<TransitionListener>) mListeners.clone();
1313                    int numListeners = tmpListeners.size();
1314                    for (int i = 0; i < numListeners; ++i) {
1315                        tmpListeners.get(i).onTransitionResume(this);
1316                    }
1317                }
1318            }
1319            mPaused = false;
1320        }
1321    }
1322
1323    /**
1324     * Called by TransitionManager to play the transition. This calls
1325     * createAnimators() to set things up and create all of the animations and then
1326     * runAnimations() to actually start the animations.
1327     */
1328    void playTransition(ViewGroup sceneRoot) {
1329        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1330        int numOldAnims = runningAnimators.size();
1331        for (int i = numOldAnims - 1; i >= 0; i--) {
1332            Animator anim = runningAnimators.keyAt(i);
1333            if (anim != null) {
1334                AnimationInfo oldInfo = runningAnimators.get(anim);
1335                if (oldInfo != null && oldInfo.view != null &&
1336                        oldInfo.view.getContext() == sceneRoot.getContext()) {
1337                    boolean cancel = false;
1338                    TransitionValues oldValues = oldInfo.values;
1339                    View oldView = oldInfo.view;
1340                    TransitionValues newValues = mEndValues.viewValues != null ?
1341                            mEndValues.viewValues.get(oldView) : null;
1342                    if (newValues == null) {
1343                        newValues = mEndValues.idValues.get(oldView.getId());
1344                    }
1345                    if (oldValues != null) {
1346                        // if oldValues null, then transition didn't care to stash values,
1347                        // and won't get canceled
1348                        if (newValues != null) {
1349                            for (String key : oldValues.values.keySet()) {
1350                                Object oldValue = oldValues.values.get(key);
1351                                Object newValue = newValues.values.get(key);
1352                                if (oldValue != null && newValue != null &&
1353                                        !oldValue.equals(newValue)) {
1354                                    cancel = true;
1355                                    if (DBG) {
1356                                        Log.d(LOG_TAG, "Transition.playTransition: " +
1357                                                "oldValue != newValue for " + key +
1358                                                ": old, new = " + oldValue + ", " + newValue);
1359                                    }
1360                                    break;
1361                                }
1362                            }
1363                        }
1364                    }
1365                    if (cancel) {
1366                        if (anim.isRunning() || anim.isStarted()) {
1367                            if (DBG) {
1368                                Log.d(LOG_TAG, "Canceling anim " + anim);
1369                            }
1370                            anim.cancel();
1371                        } else {
1372                            if (DBG) {
1373                                Log.d(LOG_TAG, "removing anim from info list: " + anim);
1374                            }
1375                            runningAnimators.remove(anim);
1376                        }
1377                    }
1378                }
1379            }
1380        }
1381
1382        createAnimators(sceneRoot, mStartValues, mEndValues);
1383        runAnimators();
1384    }
1385
1386    /**
1387     * This is a utility method used by subclasses to handle standard parts of
1388     * setting up and running an Animator: it sets the {@link #getDuration()
1389     * duration} and the {@link #getStartDelay() startDelay}, starts the
1390     * animation, and, when the animator ends, calls {@link #end()}.
1391     *
1392     * @param animator The Animator to be run during this transition.
1393     *
1394     * @hide
1395     */
1396    protected void animate(Animator animator) {
1397        // TODO: maybe pass auto-end as a boolean parameter?
1398        if (animator == null) {
1399            end();
1400        } else {
1401            if (getDuration() >= 0) {
1402                animator.setDuration(getDuration());
1403            }
1404            if (getStartDelay() >= 0) {
1405                animator.setStartDelay(getStartDelay() + animator.getStartDelay());
1406            }
1407            if (getInterpolator() != null) {
1408                animator.setInterpolator(getInterpolator());
1409            }
1410            animator.addListener(new AnimatorListenerAdapter() {
1411                @Override
1412                public void onAnimationEnd(Animator animation) {
1413                    end();
1414                    animation.removeListener(this);
1415                }
1416            });
1417            animator.start();
1418        }
1419    }
1420
1421    /**
1422     * This method is called automatically by the transition and
1423     * TransitionSet classes prior to a Transition subclass starting;
1424     * subclasses should not need to call it directly.
1425     *
1426     * @hide
1427     */
1428    protected void start() {
1429        if (mNumInstances == 0) {
1430            if (mListeners != null && mListeners.size() > 0) {
1431                ArrayList<TransitionListener> tmpListeners =
1432                        (ArrayList<TransitionListener>) mListeners.clone();
1433                int numListeners = tmpListeners.size();
1434                for (int i = 0; i < numListeners; ++i) {
1435                    tmpListeners.get(i).onTransitionStart(this);
1436                }
1437            }
1438            mEnded = false;
1439        }
1440        mNumInstances++;
1441    }
1442
1443    /**
1444     * This method is called automatically by the Transition and
1445     * TransitionSet classes when a transition finishes, either because
1446     * a transition did nothing (returned a null Animator from
1447     * {@link Transition#createAnimator(ViewGroup, TransitionValues,
1448     * TransitionValues)}) or because the transition returned a valid
1449     * Animator and end() was called in the onAnimationEnd()
1450     * callback of the AnimatorListener.
1451     *
1452     * @hide
1453     */
1454    protected void end() {
1455        --mNumInstances;
1456        if (mNumInstances == 0) {
1457            if (mListeners != null && mListeners.size() > 0) {
1458                ArrayList<TransitionListener> tmpListeners =
1459                        (ArrayList<TransitionListener>) mListeners.clone();
1460                int numListeners = tmpListeners.size();
1461                for (int i = 0; i < numListeners; ++i) {
1462                    tmpListeners.get(i).onTransitionEnd(this);
1463                }
1464            }
1465            for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) {
1466                TransitionValues tv = mStartValues.itemIdValues.valueAt(i);
1467                View v = tv.view;
1468                if (v.hasTransientState()) {
1469                    v.setHasTransientState(false);
1470                }
1471            }
1472            for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) {
1473                TransitionValues tv = mEndValues.itemIdValues.valueAt(i);
1474                View v = tv.view;
1475                if (v.hasTransientState()) {
1476                    v.setHasTransientState(false);
1477                }
1478            }
1479            mEnded = true;
1480        }
1481    }
1482
1483    /**
1484     * This method cancels a transition that is currently running.
1485     *
1486     * @hide
1487     */
1488    protected void cancel() {
1489        int numAnimators = mCurrentAnimators.size();
1490        for (int i = numAnimators - 1; i >= 0; i--) {
1491            Animator animator = mCurrentAnimators.get(i);
1492            animator.cancel();
1493        }
1494        if (mListeners != null && mListeners.size() > 0) {
1495            ArrayList<TransitionListener> tmpListeners =
1496                    (ArrayList<TransitionListener>) mListeners.clone();
1497            int numListeners = tmpListeners.size();
1498            for (int i = 0; i < numListeners; ++i) {
1499                tmpListeners.get(i).onTransitionCancel(this);
1500            }
1501        }
1502    }
1503
1504    /**
1505     * Adds a listener to the set of listeners that are sent events through the
1506     * life of an animation, such as start, repeat, and end.
1507     *
1508     * @param listener the listener to be added to the current set of listeners
1509     * for this animation.
1510     * @return This transition object.
1511     */
1512    public Transition addListener(TransitionListener listener) {
1513        if (mListeners == null) {
1514            mListeners = new ArrayList<TransitionListener>();
1515        }
1516        mListeners.add(listener);
1517        return this;
1518    }
1519
1520    /**
1521     * Removes a listener from the set listening to this animation.
1522     *
1523     * @param listener the listener to be removed from the current set of
1524     * listeners for this transition.
1525     * @return This transition object.
1526     */
1527    public Transition removeListener(TransitionListener listener) {
1528        if (mListeners == null) {
1529            return this;
1530        }
1531        mListeners.remove(listener);
1532        if (mListeners.size() == 0) {
1533            mListeners = null;
1534        }
1535        return this;
1536    }
1537
1538    /**
1539     * Sets the callback to use to find the epicenter of a Transition. A null value indicates
1540     * that there is no epicenter in the Transition and getEpicenter() will return null.
1541     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
1542     * the direction of travel. This is called the epicenter of the Transition and is
1543     * typically centered on a touched View. The
1544     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
1545     * dynamically retrieve the epicenter during a Transition.
1546     * @param epicenterCallback The callback to use to find the epicenter of the Transition.
1547     */
1548    public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
1549        mEpicenterCallback = epicenterCallback;
1550    }
1551
1552    /**
1553     * Returns the callback used to find the epicenter of the Transition.
1554     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
1555     * the direction of travel. This is called the epicenter of the Transition and is
1556     * typically centered on a touched View. The
1557     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
1558     * dynamically retrieve the epicenter during a Transition.
1559     * @return the callback used to find the epicenter of the Transition.
1560     */
1561    public EpicenterCallback getEpicenterCallback() {
1562        return mEpicenterCallback;
1563    }
1564
1565    /**
1566     * Returns the epicenter as specified by the
1567     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
1568     * @return the epicenter as specified by the
1569     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
1570     * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
1571     */
1572    public Rect getEpicenter() {
1573        if (mEpicenterCallback == null) {
1574            return null;
1575        }
1576        return mEpicenterCallback.getEpicenter(this);
1577    }
1578
1579    /**
1580     * Sets the method for determining Animator start delays.
1581     * When a Transition affects several Views like {@link android.transition.Explode} or
1582     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
1583     * such that the Animator start delay depends on position of the View. The
1584     * TransitionPropagation specifies how the start delays are calculated.
1585     * @param transitionPropagation The class used to determine the start delay of
1586     *                              Animators created by this Transition. A null value
1587     *                              indicates that no delay should be used.
1588     */
1589    public void setPropagation(TransitionPropagation transitionPropagation) {
1590        mPropagation = transitionPropagation;
1591    }
1592
1593    /**
1594     * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator start
1595     * delays.
1596     * When a Transition affects several Views like {@link android.transition.Explode} or
1597     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
1598     * such that the Animator start delay depends on position of the View. The
1599     * TransitionPropagation specifies how the start delays are calculated.
1600     * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start
1601     * delays. This is null by default.
1602     */
1603    public TransitionPropagation getPropagation() {
1604        return mPropagation;
1605    }
1606
1607    /**
1608     * Captures TransitionPropagation values for the given view and the
1609     * hierarchy underneath it.
1610     */
1611    void capturePropagationValues(TransitionValues transitionValues) {
1612        if (mPropagation != null && !transitionValues.values.isEmpty()) {
1613            String[] propertyNames = mPropagation.getPropagationProperties();
1614            if (propertyNames == null) {
1615                return;
1616            }
1617            boolean containsAll = true;
1618            for (int i = 0; i < propertyNames.length; i++) {
1619                if (!transitionValues.values.containsKey(propertyNames[i])) {
1620                    containsAll = false;
1621                    break;
1622                }
1623            }
1624            if (!containsAll) {
1625                mPropagation.captureValues(transitionValues);
1626            }
1627        }
1628    }
1629
1630    Transition setSceneRoot(ViewGroup sceneRoot) {
1631        mSceneRoot = sceneRoot;
1632        return this;
1633    }
1634
1635    void setCanRemoveViews(boolean canRemoveViews) {
1636        mCanRemoveViews = canRemoveViews;
1637    }
1638
1639    public boolean canRemoveViews() {
1640        return mCanRemoveViews;
1641    }
1642
1643    @Override
1644    public String toString() {
1645        return toString("");
1646    }
1647
1648    @Override
1649    public Transition clone() {
1650        Transition clone = null;
1651        try {
1652            clone = (Transition) super.clone();
1653            clone.mAnimators = new ArrayList<Animator>();
1654            clone.mStartValues = new TransitionValuesMaps();
1655            clone.mEndValues = new TransitionValuesMaps();
1656        } catch (CloneNotSupportedException e) {}
1657
1658        return clone;
1659    }
1660
1661    /**
1662     * Returns the name of this Transition. This name is used internally to distinguish
1663     * between different transitions to determine when interrupting transitions overlap.
1664     * For example, a ChangeBounds running on the same target view as another ChangeBounds
1665     * should determine whether the old transition is animating to different end values
1666     * and should be canceled in favor of the new transition.
1667     *
1668     * <p>By default, a Transition's name is simply the value of {@link Class#getName()},
1669     * but subclasses are free to override and return something different.</p>
1670     *
1671     * @return The name of this transition.
1672     */
1673    public String getName() {
1674        return mName;
1675    }
1676
1677    String toString(String indent) {
1678        String result = indent + getClass().getSimpleName() + "@" +
1679                Integer.toHexString(hashCode()) + ": ";
1680        if (mDuration != -1) {
1681            result += "dur(" + mDuration + ") ";
1682        }
1683        if (mStartDelay != -1) {
1684            result += "dly(" + mStartDelay + ") ";
1685        }
1686        if (mInterpolator != null) {
1687            result += "interp(" + mInterpolator + ") ";
1688        }
1689        if (mTargetIds.size() > 0 || mTargets.size() > 0) {
1690            result += "tgts(";
1691            if (mTargetIds.size() > 0) {
1692                for (int i = 0; i < mTargetIds.size(); ++i) {
1693                    if (i > 0) {
1694                        result += ", ";
1695                    }
1696                    result += mTargetIds.get(i);
1697                }
1698            }
1699            if (mTargets.size() > 0) {
1700                for (int i = 0; i < mTargets.size(); ++i) {
1701                    if (i > 0) {
1702                        result += ", ";
1703                    }
1704                    result += mTargets.get(i);
1705                }
1706            }
1707            result += ")";
1708        }
1709        return result;
1710    }
1711
1712    /**
1713     * A transition listener receives notifications from a transition.
1714     * Notifications indicate transition lifecycle events.
1715     */
1716    public static interface TransitionListener {
1717        /**
1718         * Notification about the start of the transition.
1719         *
1720         * @param transition The started transition.
1721         */
1722        void onTransitionStart(Transition transition);
1723
1724        /**
1725         * Notification about the end of the transition. Canceled transitions
1726         * will always notify listeners of both the cancellation and end
1727         * events. That is, {@link #onTransitionEnd(Transition)} is always called,
1728         * regardless of whether the transition was canceled or played
1729         * through to completion.
1730         *
1731         * @param transition The transition which reached its end.
1732         */
1733        void onTransitionEnd(Transition transition);
1734
1735        /**
1736         * Notification about the cancellation of the transition.
1737         * Note that cancel 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 on target objects which was set at
1740         * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
1741         * createAnimator()} time.
1742         *
1743         * @param transition The transition which was canceled.
1744         */
1745        void onTransitionCancel(Transition transition);
1746
1747        /**
1748         * Notification when a transition is paused.
1749         * Note that createAnimator() may be called by a parent {@link TransitionSet} on
1750         * a child transition which has not yet started. This allows the child
1751         * transition to restore state on target objects which was set at
1752         * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
1753         * createAnimator()} time.
1754         *
1755         * @param transition The transition which was paused.
1756         */
1757        void onTransitionPause(Transition transition);
1758
1759        /**
1760         * Notification when a transition is resumed.
1761         * Note that resume() may be called by a parent {@link TransitionSet} on
1762         * a child transition which has not yet started. This allows the child
1763         * transition to restore state which may have changed in an earlier call
1764         * to {@link #onTransitionPause(Transition)}.
1765         *
1766         * @param transition The transition which was resumed.
1767         */
1768        void onTransitionResume(Transition transition);
1769    }
1770
1771    /**
1772     * Utility adapter class to avoid having to override all three methods
1773     * whenever someone just wants to listen for a single event.
1774     *
1775     * @hide
1776     * */
1777    public static class TransitionListenerAdapter implements TransitionListener {
1778        @Override
1779        public void onTransitionStart(Transition transition) {
1780        }
1781
1782        @Override
1783        public void onTransitionEnd(Transition transition) {
1784        }
1785
1786        @Override
1787        public void onTransitionCancel(Transition transition) {
1788        }
1789
1790        @Override
1791        public void onTransitionPause(Transition transition) {
1792        }
1793
1794        @Override
1795        public void onTransitionResume(Transition transition) {
1796        }
1797    }
1798
1799    /**
1800     * Holds information about each animator used when a new transition starts
1801     * while other transitions are still running to determine whether a running
1802     * animation should be canceled or a new animation noop'd. The structure holds
1803     * information about the state that an animation is going to, to be compared to
1804     * end state of a new animation.
1805     * @hide
1806     */
1807    public static class AnimationInfo {
1808        public View view;
1809        String name;
1810        TransitionValues values;
1811        WindowId windowId;
1812
1813        AnimationInfo(View view, String name, WindowId windowId, TransitionValues values) {
1814            this.view = view;
1815            this.name = name;
1816            this.values = values;
1817            this.windowId = windowId;
1818        }
1819    }
1820
1821    /**
1822     * Utility class for managing typed ArrayLists efficiently. In particular, this
1823     * can be useful for lists that we don't expect to be used often (eg, the exclude
1824     * lists), so we'd like to keep them nulled out by default. This causes the code to
1825     * become tedious, with constant null checks, code to allocate when necessary,
1826     * and code to null out the reference when the list is empty. This class encapsulates
1827     * all of that functionality into simple add()/remove() methods which perform the
1828     * necessary checks, allocation/null-out as appropriate, and return the
1829     * resulting list.
1830     */
1831    private static class ArrayListManager {
1832
1833        /**
1834         * Add the specified item to the list, returning the resulting list.
1835         * The returned list can either the be same list passed in or, if that
1836         * list was null, the new list that was created.
1837         *
1838         * Note that the list holds unique items; if the item already exists in the
1839         * list, the list is not modified.
1840         */
1841        static <T> ArrayList<T> add(ArrayList<T> list, T item) {
1842            if (list == null) {
1843                list = new ArrayList<T>();
1844            }
1845            if (!list.contains(item)) {
1846                list.add(item);
1847            }
1848            return list;
1849        }
1850
1851        /**
1852         * Remove the specified item from the list, returning the resulting list.
1853         * The returned list can either the be same list passed in or, if that
1854         * list becomes empty as a result of the remove(), the new list was created.
1855         */
1856        static <T> ArrayList<T> remove(ArrayList<T> list, T item) {
1857            if (list != null) {
1858                list.remove(item);
1859                if (list.isEmpty()) {
1860                    list = null;
1861                }
1862            }
1863            return list;
1864        }
1865    }
1866
1867    /**
1868     * Class to get the epicenter of Transition. Use
1869     * {@link #setEpicenterCallback(android.transition.Transition.EpicenterCallback)} to
1870     * set the callback used to calculate the epicenter of the Transition. Override
1871     * {@link #getEpicenter()} to return the rectangular region in screen coordinates of
1872     * the epicenter of the transition.
1873     * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
1874     */
1875    public static abstract class EpicenterCallback {
1876
1877        /**
1878         * Implementers must override to return the epicenter of the Transition in screen
1879         * coordinates. Transitions like {@link android.transition.Explode} depend upon
1880         * an epicenter for the Transition. In Explode, Views move toward or away from the
1881         * center of the epicenter Rect along the vector between the epicenter and the center
1882         * of the View appearing and disappearing. Some Transitions, such as
1883         * {@link android.transition.Fade} pay no attention to the epicenter.
1884         *
1885         * @param transition The transition for which the epicenter applies.
1886         * @return The Rect region of the epicenter of <code>transition</code> or null if
1887         * there is no epicenter.
1888         */
1889        public abstract Rect getEpicenter(Transition transition);
1890    }
1891}
1892