blob: 7836268b067b03749b6ddd0726d14cbb59bc6bbd [file] [log] [blame]
Chet Haasefaebd8f2012-05-18 14:17:57 -07001/*
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 */
Chet Haase6ebe3de2013-06-17 16:50:50 -070016
Chet Haasefaebd8f2012-05-18 14:17:57 -070017package android.view.transition;
18
Chet Haasee9d32ea2013-06-04 08:46:42 -070019import android.util.ArrayMap;
Chet Haasec43524f2013-07-16 14:40:11 -070020import android.util.Log;
Chet Haasefaebd8f2012-05-18 14:17:57 -070021import android.view.ViewGroup;
22import android.view.ViewTreeObserver;
23
Chet Haase4f507232013-06-10 13:00:06 -070024import java.util.ArrayList;
25
Chet Haasefaebd8f2012-05-18 14:17:57 -070026/**
27 * This class manages the set of transitions that fire when there is a
28 * change of {@link Scene}. To use the manager, add scenes along with
29 * transition objects with calls to {@link #setTransition(Scene, Transition)}
30 * or {@link #setTransition(Scene, Scene, Transition)}. Setting specific
31 * transitions for scene changes is not required; by default, a Scene change
32 * will use {@link AutoTransition} to do something reasonable for most
33 * situations. Specifying other transitions for particular scene changes is
34 * only necessary if the application wants different transition behavior
35 * in these situations.
36 */
37public class TransitionManager {
38 // TODO: how to handle enter/exit?
39
Chet Haasec43524f2013-07-16 14:40:11 -070040 private static String LOG_TAG = "TransitionManager";
41
Chet Haasefaebd8f2012-05-18 14:17:57 -070042 private static final Transition sDefaultTransition = new AutoTransition();
43 private Transition mDefaultTransition = new AutoTransition();
44
Chet Haase08735182013-06-04 10:44:40 -070045 ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
46 ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
47 new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
Chet Haasee9d32ea2013-06-04 08:46:42 -070048 static ArrayMap<ViewGroup, Transition> sRunningTransitions =
49 new ArrayMap<ViewGroup, Transition>();
Chet Haase4f507232013-06-10 13:00:06 -070050 private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>();
Chet Haasefaebd8f2012-05-18 14:17:57 -070051
Chet Haase4f507232013-06-10 13:00:06 -070052
53 /**
Chet Haasefaebd8f2012-05-18 14:17:57 -070054 * Sets the transition to be used for any scene change for which no
55 * other transition is explicitly set. The initial value is
56 * an {@link AutoTransition} instance.
57 *
58 * @param transition The default transition to be used for scene changes.
59 */
60 public void setDefaultTransition(Transition transition) {
61 mDefaultTransition = transition;
62 }
63
64 /**
65 * Gets the current default transition. The initial value is an {@link
66 * AutoTransition} instance.
67 *
68 * @return The current default transition.
69 * @see #setDefaultTransition(Transition)
70 */
71 public Transition getDefaultTransition() {
72 return mDefaultTransition;
73 }
74
75 /**
76 * Sets a specific transition to occur when the given scene is entered.
77 *
78 * @param scene The scene which, when applied, will cause the given
79 * transition to run.
80 * @param transition The transition that will play when the given scene is
81 * entered. A value of null will result in the default behavior of
82 * using {@link AutoTransition}.
83 */
84 public void setTransition(Scene scene, Transition transition) {
85 mSceneTransitions.put(scene, transition);
86 }
87
88 /**
89 * Sets a specific transition to occur when the given pair of scenes is
90 * exited/entered.
91 *
92 * @param fromScene The scene being exited when the given transition will
93 * be run
94 * @param toScene The scene being entered when the given transition will
95 * be run
96 * @param transition The transition that will play when the given scene is
97 * entered. A value of null will result in the default behavior of
98 * using {@link AutoTransition}.
99 */
100 public void setTransition(Scene fromScene, Scene toScene, Transition transition) {
Chet Haase08735182013-06-04 10:44:40 -0700101 ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700102 if (sceneTransitionMap == null) {
Chet Haase08735182013-06-04 10:44:40 -0700103 sceneTransitionMap = new ArrayMap<Scene, Transition>();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700104 mScenePairTransitions.put(toScene, sceneTransitionMap);
105 }
106 sceneTransitionMap.put(fromScene, transition);
107 }
108
109 /**
110 * Returns the Transition for the given scene being entered. The result
111 * depends not only on the given scene, but also the scene which the
112 * {@link Scene#getSceneRoot() sceneRoot} of the Scene is currently in.
113 *
114 * @param scene The scene being entered
115 * @return The Transition to be used for the given scene change. If no
116 * Transition was specified for this scene change, {@link AutoTransition}
117 * will be used instead.
118 */
119 private Transition getTransition(Scene scene) {
120 Transition transition = null;
121 ViewGroup sceneRoot = scene.getSceneRoot();
122 if (sceneRoot != null) {
123 // TODO: cached in Scene instead? long-term, cache in View itself
124 Scene currScene = sceneRoot.getCurrentScene();
125 if (currScene != null) {
Chet Haase08735182013-06-04 10:44:40 -0700126 ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(scene);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700127 if (sceneTransitionMap != null) {
128 transition = sceneTransitionMap.get(currScene);
129 if (transition != null) {
130 return transition;
131 }
132 }
133 }
134 }
135 transition = mSceneTransitions.get(scene);
136 return (transition != null) ? transition : new AutoTransition();
137 }
138
139 /**
140 * This is where all of the work of a transition/scene-change is
141 * orchestrated. This method captures the start values for the given
142 * transition, exits the current Scene, enters the new scene, captures
143 * the end values for the transition, and finally plays the
144 * resulting values-populated transition.
145 *
146 * @param scene The scene being entered
147 * @param transition The transition to play for this scene change
148 */
Chet Haase6ebe3de2013-06-17 16:50:50 -0700149 private static void changeScene(Scene scene, Transition transition) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700150
151 final ViewGroup sceneRoot = scene.getSceneRoot();
152
Chet Haase6ebe3de2013-06-17 16:50:50 -0700153 Transition transitionClone = transition.clone();
154 transitionClone.setSceneRoot(sceneRoot);
155
156 sceneChangeSetup(sceneRoot, transitionClone);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700157
158 scene.enter();
159
Chet Haase6ebe3de2013-06-17 16:50:50 -0700160 sceneChangeRunTransition(sceneRoot, transitionClone);
Chet Haase4f507232013-06-10 13:00:06 -0700161 }
162
163 private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
164 final Transition transition) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700165 if (transition != null) {
166 final ViewTreeObserver observer = sceneRoot.getViewTreeObserver();
167 observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
168 public boolean onPreDraw() {
169 sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
Chet Haasec43524f2013-07-16 14:40:11 -0700170 sPendingTransitions.remove(sceneRoot);
Chet Haasee9d32ea2013-06-04 08:46:42 -0700171 // Add to running list, handle end to remove it
172 sRunningTransitions.put(sceneRoot, transition);
173 transition.addListener(new Transition.TransitionListenerAdapter() {
174 @Override
175 public void onTransitionEnd(Transition transition) {
176 sRunningTransitions.remove(sceneRoot);
177 }
178 });
Chet Haasefaebd8f2012-05-18 14:17:57 -0700179 transition.captureValues(sceneRoot, false);
Chet Haase6ebe3de2013-06-17 16:50:50 -0700180 transition.playTransition(sceneRoot);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700181 return true;
182 }
183 });
184 }
185 }
186
Chet Haase4f507232013-06-10 13:00:06 -0700187 private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
188
Chet Haase4f507232013-06-10 13:00:06 -0700189 // Capture current values
Chet Haasec81a8492013-07-12 12:54:38 -0700190 Transition runningTransition = sRunningTransitions.get(sceneRoot);
191
Chet Haase4f507232013-06-10 13:00:06 -0700192 if (transition != null) {
193 transition.captureValues(sceneRoot, true);
194 }
195
Chet Haasec81a8492013-07-12 12:54:38 -0700196 if (runningTransition != null) {
197 runningTransition.cancelTransition();
198 }
199
Chet Haase4f507232013-06-10 13:00:06 -0700200 // Notify previous scene that it is being exited
201 Scene previousScene = sceneRoot.getCurrentScene();
202 if (previousScene != null) {
203 previousScene.exit();
204 }
205 }
206
Chet Haasefaebd8f2012-05-18 14:17:57 -0700207 /**
208 * Change to the given scene, using the
209 * appropriate transition for this particular scene change
210 * (as specified to the TransitionManager, or the default
211 * if no such transition exists).
212 *
213 * @param scene The Scene to change to
214 */
215 public void transitionTo(Scene scene) {
216 // Auto transition if there is no transition declared for the Scene, but there is
217 // a root or parent view
218 changeScene(scene, getTransition(scene));
219
220 }
221
222 /**
223 * Static utility method to simply change to the given scene using
224 * the default transition for TransitionManager.
225 *
226 * @param scene The Scene to change to
227 */
228 public static void go(Scene scene) {
229 changeScene(scene, sDefaultTransition);
230 }
231
232 /**
233 * Static utility method to simply change to the given scene using
234 * the given transition.
235 *
236 * <p>Passing in <code>null</code> for the transition parameter will
237 * result in the scene changing without any transition running, and is
238 * equivalent to calling {@link Scene#exit()} on the scene root's
239 * {@link ViewGroup#getCurrentScene() current scene}, followed by
240 * {@link Scene#enter()} on the scene specified by the <code>scene</code>
241 * parameter.</p>
242 *
243 * @param scene The Scene to change to
244 * @param transition The transition to use for this scene change. A
245 * value of null causes the scene change to happen with no transition.
246 */
247 public static void go(Scene scene, Transition transition) {
248 changeScene(scene, transition);
249 }
250
251 /**
252 * Static utility method to simply change to a scene defined by the
253 * code in the given runnable, which will be executed after
254 * the current values have been captured for the transition.
255 * This is equivalent to creating a Scene and calling {@link
256 * Scene#setEnterAction(Runnable)} with the runnable, then calling
257 * {@link #go(Scene, Transition)}. The transition used will be the
258 * default provided by TransitionManager.
259 *
260 * @param sceneRoot The root of the View hierarchy used when this scene
261 * runs a transition automatically.
262 * @param action The runnable whose {@link Runnable#run() run()} method will
263 * be called.
264 */
265 public static void go(ViewGroup sceneRoot, Runnable action) {
266 Scene scene = new Scene(sceneRoot);
267 scene.setEnterAction(action);
268 changeScene(scene, sDefaultTransition);
269 }
270
271 /**
272 * Static utility method to simply change to a scene defined by the
273 * code in the given runnable, which will be executed after
274 * the current values have been captured for the transition.
275 * This is equivalent to creating a Scene and calling {@link
276 * Scene#setEnterAction(Runnable)} with the runnable, then calling
277 * {@link #go(Scene, Transition)}. The given transition will be
278 * used to animate the changes.
279 *
280 * <p>Passing in <code>null</code> for the transition parameter will
281 * result in the scene changing without any transition running, and is
282 * equivalent to calling {@link Scene#exit()} on the scene root's
283 * {@link ViewGroup#getCurrentScene() current scene}, followed by
284 * {@link Scene#enter()} on a new scene specified by the
285 * <code>action</code> parameter.</p>
286 *
287 * @param sceneRoot The root of the View hierarchy to run the transition on.
288 * @param action The runnable whose {@link Runnable#run() run()} method will
289 * be called.
290 * @param transition The transition to use for this change. A
291 * value of null causes the change to happen with no transition.
292 */
293 public static void go(ViewGroup sceneRoot, Runnable action, Transition transition) {
294 Scene scene = new Scene(sceneRoot);
295 scene.setEnterAction(action);
296 changeScene(scene, transition);
297 }
Chet Haase4f507232013-06-10 13:00:06 -0700298
299 /**
300 * Static utility method to animate to a new scene defined by all changes within
301 * the given scene root between calling this method and the next rendering frame.
302 * Calling this method causes TransitionManager to capture current values in the
303 * scene root and then post a request to run a transition on the next frame.
304 * At that time, the new values in the scene root will be captured and changes
305 * will be animated. There is no need to create a Scene; it is implied by
306 * changes which take place between calling this method and the next frame when
307 * the transition begins.
308 *
309 * <p>Calling this method several times before the next frame (for example, if
310 * unrelated code also wants to make dynamic changes and run a transition on
311 * the same scene root), only the first call will trigger capturing values
312 * and exiting the current scene. Subsequent calls to the method with the
313 * same scene root during the same frame will be ignored.</p>
314 *
315 * <p>Passing in <code>null</code> for the transition parameter will
316 * cause the TransitionManager to use its default transition.</p>
317 *
318 * @param sceneRoot The root of the View hierarchy to run the transition on.
319 * @param transition The transition to use for this change. A
320 * value of null causes the TransitionManager to use the default transition.
321 */
322 public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
Chet Haase7a46dde2013-07-17 10:22:53 -0700323 if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
Chet Haasec43524f2013-07-16 14:40:11 -0700324 if (Transition.DBG) {
325 Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
326 sceneRoot + ", " + transition);
327 }
Chet Haase4f507232013-06-10 13:00:06 -0700328 sPendingTransitions.add(sceneRoot);
329 if (transition == null) {
330 transition = sDefaultTransition;
331 }
Chet Haase6ebe3de2013-06-17 16:50:50 -0700332 final Transition finalTransition = transition.clone();
Chet Haase4f507232013-06-10 13:00:06 -0700333 sceneChangeSetup(sceneRoot, transition);
334 sceneRoot.setCurrentScene(null);
Chet Haasec43524f2013-07-16 14:40:11 -0700335 sceneChangeRunTransition(sceneRoot, finalTransition);
Chet Haase4f507232013-06-10 13:00:06 -0700336 }
337 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700338}