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