TransitionManager.java revision aa006133228facf2f9b61e0492fd8f724fac472f
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.content.Context;
20import android.util.ArrayMap;
21import android.util.Log;
22import android.view.ViewGroup;
23import android.view.ViewTreeObserver;
24
25import java.lang.ref.WeakReference;
26import java.util.ArrayList;
27
28/**
29 * This class manages the set of transitions that fire when there is a
30 * change of {@link Scene}. To use the manager, add scenes along with
31 * transition objects with calls to {@link #setTransition(Scene, Transition)}
32 * or {@link #setTransition(Scene, Scene, Transition)}. Setting specific
33 * transitions for scene changes is not required; by default, a Scene change
34 * will use {@link AutoTransition} to do something reasonable for most
35 * situations. Specifying other transitions for particular scene changes is
36 * only necessary if the application wants different transition behavior
37 * in these situations.
38 *
39 * <p>TransitionManagers can be declared in XML resource files inside the
40 * <code>res/transition</code> directory. TransitionManager resources consist of
41 * the <code>transitionManager</code>tag name, containing one or more
42 * <code>transition</code> tags, each of which describe the relationship of
43 * that transition to the from/to scene information in that tag.
44 * For example, here is a resource file that declares several scene
45 * transitions:</p>
46 *
47 * {@sample development/samples/ApiDemos/res/transition/transitions_mgr.xml TransitionManager}
48 *
49 * <p>For each of the <code>fromScene</code> and <code>toScene</code> attributes,
50 * there is a reference to a standard XML layout file. This is equivalent to
51 * creating a scene from a layout in code by calling
52 * {@link Scene#getSceneForLayout(ViewGroup, int, Context)}. For the
53 * <code>transition</code> attribute, there is a reference to a resource
54 * file in the <code>res/transition</code> directory which describes that
55 * transition.</p>
56 *
57 * Information on XML resource descriptions for transitions can be found for
58 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
59 * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade},
60 * and {@link android.R.styleable#TransitionManager}.
61 */
62public class TransitionManager {
63    // TODO: how to handle enter/exit?
64
65    private static String LOG_TAG = "TransitionManager";
66
67    private static Transition sDefaultTransition = new AutoTransition();
68
69    ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
70    ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
71            new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
72    private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
73            sRunningTransitions =
74            new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>();
75    private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>();
76
77
78    /**
79     * Sets the transition to be used for any scene change for which no
80     * other transition is explicitly set. The initial value is
81     * an {@link AutoTransition} instance.
82     *
83     * @param transition The default transition to be used for scene changes.
84     */
85    public void setDefaultTransition(Transition transition) {
86        sDefaultTransition = transition;
87    }
88
89    /**
90     * Gets the current default transition. The initial value is an {@link
91     * AutoTransition} instance.
92     *
93     * @return The current default transition.
94     * @see #setDefaultTransition(Transition)
95     */
96    public static Transition getDefaultTransition() {
97        return sDefaultTransition;
98    }
99
100    /**
101     * Sets a specific transition to occur when the given scene is entered.
102     *
103     * @param scene The scene which, when applied, will cause the given
104     * transition to run.
105     * @param transition The transition that will play when the given scene is
106     * entered. A value of null will result in the default behavior of
107     * using the {@link #getDefaultTransition() default transition} instead.
108     */
109    public void setTransition(Scene scene, Transition transition) {
110        mSceneTransitions.put(scene, transition);
111    }
112
113    /**
114     * Sets a specific transition to occur when the given pair of scenes is
115     * exited/entered.
116     *
117     * @param fromScene The scene being exited when the given transition will
118     * be run
119     * @param toScene The scene being entered when the given transition will
120     * be run
121     * @param transition The transition that will play when the given scene is
122     * entered. A value of null will result in the default behavior of
123     * using the {@link #getDefaultTransition() default transition} instead.
124     */
125    public void setTransition(Scene fromScene, Scene toScene, Transition transition) {
126        ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene);
127        if (sceneTransitionMap == null) {
128            sceneTransitionMap = new ArrayMap<Scene, Transition>();
129            mScenePairTransitions.put(toScene, sceneTransitionMap);
130        }
131        sceneTransitionMap.put(fromScene, transition);
132    }
133
134    /**
135     * Returns the Transition for the given scene being entered. The result
136     * depends not only on the given scene, but also the scene which the
137     * {@link Scene#getSceneRoot() sceneRoot} of the Scene is currently in.
138     *
139     * @param scene The scene being entered
140     * @return The Transition to be used for the given scene change. If no
141     * Transition was specified for this scene change, the {@link #getDefaultTransition()
142     * default transition} will be used instead.
143     */
144    private Transition getTransition(Scene scene) {
145        Transition transition = null;
146        ViewGroup sceneRoot = scene.getSceneRoot();
147        if (sceneRoot != null) {
148            // TODO: cached in Scene instead? long-term, cache in View itself
149            Scene currScene = Scene.getCurrentScene(sceneRoot);
150            if (currScene != null) {
151                ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(scene);
152                if (sceneTransitionMap != null) {
153                    transition = sceneTransitionMap.get(currScene);
154                    if (transition != null) {
155                        return transition;
156                    }
157                }
158            }
159        }
160        transition = mSceneTransitions.get(scene);
161        return (transition != null) ? transition : sDefaultTransition;
162    }
163
164    /**
165     * This is where all of the work of a transition/scene-change is
166     * orchestrated. This method captures the start values for the given
167     * transition, exits the current Scene, enters the new scene, captures
168     * the end values for the transition, and finally plays the
169     * resulting values-populated transition.
170     *
171     * @param scene The scene being entered
172     * @param transition The transition to play for this scene change
173     */
174    private static void changeScene(Scene scene, Transition transition) {
175
176        final ViewGroup sceneRoot = scene.getSceneRoot();
177
178        Transition transitionClone = transition.clone();
179        transitionClone.setSceneRoot(sceneRoot);
180
181        Scene oldScene = Scene.getCurrentScene(sceneRoot);
182        if (oldScene != null && oldScene.isCreatedFromLayoutResource()) {
183            transitionClone.setCanRemoveViews(true);
184        }
185
186        sceneChangeSetup(sceneRoot, transitionClone);
187
188        scene.enter();
189
190        sceneChangeRunTransition(sceneRoot, transitionClone);
191    }
192
193    private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() {
194        WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>> runningTransitions =
195                sRunningTransitions.get();
196        if (runningTransitions == null || runningTransitions.get() == null) {
197            ArrayMap<ViewGroup, ArrayList<Transition>> transitions =
198                    new ArrayMap<ViewGroup, ArrayList<Transition>>();
199            runningTransitions = new WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>(
200                    transitions);
201            sRunningTransitions.set(runningTransitions);
202        }
203        return runningTransitions.get();
204    }
205
206    private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
207            final Transition transition) {
208        if (transition != null) {
209            final ViewTreeObserver observer = sceneRoot.getViewTreeObserver();
210            final ViewTreeObserver.OnPreDrawListener listener =
211                    new ViewTreeObserver.OnPreDrawListener() {
212                public boolean onPreDraw() {
213                    sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
214                    sPendingTransitions.remove(sceneRoot);
215                    // Add to running list, handle end to remove it
216                    final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
217                            getRunningTransitions();
218                    ArrayList<Transition> currentTransitions = runningTransitions.get(sceneRoot);
219                    ArrayList<Transition> previousRunningTransitions = null;
220                    if (currentTransitions == null) {
221                        currentTransitions = new ArrayList<Transition>();
222                        runningTransitions.put(sceneRoot, currentTransitions);
223                    } else if (currentTransitions.size() > 0) {
224                        previousRunningTransitions = new ArrayList<Transition>(currentTransitions);
225                    }
226                    currentTransitions.add(transition);
227                    transition.addListener(new Transition.TransitionListenerAdapter() {
228                        @Override
229                        public void onTransitionEnd(Transition transition) {
230                            ArrayList<Transition> currentTransitions =
231                                    runningTransitions.get(sceneRoot);
232                            currentTransitions.remove(transition);
233                        }
234                    });
235                    transition.captureValues(sceneRoot, false);
236                    if (previousRunningTransitions != null) {
237                        for (Transition runningTransition : previousRunningTransitions) {
238                            runningTransition.resume();
239                        }
240                    }
241                    transition.playTransition(sceneRoot);
242
243                    return true;
244                }
245            };
246            observer.addOnPreDrawListener(listener);
247        }
248    }
249
250    private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
251
252        // Capture current values
253        ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
254
255        if (runningTransitions != null && runningTransitions.size() > 0) {
256            for (Transition runningTransition : runningTransitions) {
257                runningTransition.pause();
258            }
259        }
260
261        if (transition != null) {
262            transition.captureValues(sceneRoot, true);
263        }
264
265        // Notify previous scene that it is being exited
266        Scene previousScene = Scene.getCurrentScene(sceneRoot);
267        if (previousScene != null) {
268            previousScene.exit();
269        }
270    }
271
272    /**
273     * Change to the given scene, using the
274     * appropriate transition for this particular scene change
275     * (as specified to the TransitionManager, or the default
276     * if no such transition exists).
277     *
278     * @param scene The Scene to change to
279     */
280    public void transitionTo(Scene scene) {
281        // Auto transition if there is no transition declared for the Scene, but there is
282        // a root or parent view
283        changeScene(scene, getTransition(scene));
284
285    }
286
287    /**
288     * Convenience method to simply change to the given scene using
289     * the default transition for TransitionManager.
290     *
291     * @param scene The Scene to change to
292     */
293    public static void go(Scene scene) {
294        changeScene(scene, sDefaultTransition);
295    }
296
297    /**
298     * Convenience method to simply change to the given scene using
299     * the given transition.
300     *
301     * <p>Passing in <code>null</code> for the transition parameter will
302     * result in the scene changing without any transition running, and is
303     * equivalent to calling {@link Scene#exit()} on the scene root's
304     * current scene, followed by {@link Scene#enter()} on the scene
305     * specified by the <code>scene</code> parameter.</p>
306     *
307     * @param scene The Scene to change to
308     * @param transition The transition to use for this scene change. A
309     * value of null causes the scene change to happen with no transition.
310     */
311    public static void go(Scene scene, Transition transition) {
312        changeScene(scene, transition);
313    }
314
315    /**
316     * Convenience method to animate, using the default transition,
317     * to a new scene defined by all changes within the given scene root between
318     * calling this method and the next rendering frame.
319     * Equivalent to calling {@link #beginDelayedTransition(ViewGroup, Transition)}
320     * with a value of <code>null</code> for the <code>transition</code> parameter.
321     *
322     * @param sceneRoot The root of the View hierarchy to run the transition on.
323     */
324    public static void beginDelayedTransition(final ViewGroup sceneRoot) {
325        beginDelayedTransition(sceneRoot, null);
326    }
327
328    /**
329     * Convenience method to animate to a new scene defined by all changes within
330     * the given scene root between calling this method and the next rendering frame.
331     * Calling this method causes TransitionManager to capture current values in the
332     * scene root and then post a request to run a transition on the next frame.
333     * At that time, the new values in the scene root will be captured and changes
334     * will be animated. There is no need to create a Scene; it is implied by
335     * changes which take place between calling this method and the next frame when
336     * the transition begins.
337     *
338     * <p>Calling this method several times before the next frame (for example, if
339     * unrelated code also wants to make dynamic changes and run a transition on
340     * the same scene root), only the first call will trigger capturing values
341     * and exiting the current scene. Subsequent calls to the method with the
342     * same scene root during the same frame will be ignored.</p>
343     *
344     * <p>Passing in <code>null</code> for the transition parameter will
345     * cause the TransitionManager to use its default transition.</p>
346     *
347     * @param sceneRoot The root of the View hierarchy to run the transition on.
348     * @param transition The transition to use for this change. A
349     * value of null causes the TransitionManager to use the default transition.
350     */
351    public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
352        if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
353            if (Transition.DBG) {
354                Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
355                        sceneRoot + ", " + transition);
356            }
357            sPendingTransitions.add(sceneRoot);
358            if (transition == null) {
359                transition = sDefaultTransition;
360            }
361            final Transition transitionClone = transition.clone();
362            sceneChangeSetup(sceneRoot, transitionClone);
363            Scene.setCurrentScene(sceneRoot, null);
364            sceneChangeRunTransition(sceneRoot, transitionClone);
365        }
366    }
367}
368