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