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