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