blob: 3bf67902e98622ccb18c06e7a2d56039eb24c63c [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 Haased82c8ac2013-08-26 14:20:16 -070017package android.transition;
Chet Haasefaebd8f2012-05-18 14:17:57 -070018
Chet Haased82c8ac2013-08-26 14:20:16 -070019import android.content.Context;
Chet Haasee9d32ea2013-06-04 08:46:42 -070020import android.util.ArrayMap;
Chet Haasec43524f2013-07-16 14:40:11 -070021import android.util.Log;
Chet Haasedf32aa82013-10-21 17:19:37 -070022import android.view.View;
Chet Haasefaebd8f2012-05-18 14:17:57 -070023import android.view.ViewGroup;
24import android.view.ViewTreeObserver;
25
Chet Haase7660d122013-09-13 13:29:31 -070026import java.lang.ref.WeakReference;
Chet Haase4f507232013-06-10 13:00:06 -070027import java.util.ArrayList;
28
Chet Haasefaebd8f2012-05-18 14:17:57 -070029/**
30 * This class manages the set of transitions that fire when there is a
31 * change of {@link Scene}. To use the manager, add scenes along with
32 * transition objects with calls to {@link #setTransition(Scene, Transition)}
33 * or {@link #setTransition(Scene, Scene, Transition)}. Setting specific
34 * transitions for scene changes is not required; by default, a Scene change
35 * will use {@link AutoTransition} to do something reasonable for most
36 * situations. Specifying other transitions for particular scene changes is
37 * only necessary if the application wants different transition behavior
38 * in these situations.
Chet Haased82c8ac2013-08-26 14:20:16 -070039 *
40 * <p>TransitionManagers can be declared in XML resource files inside the
41 * <code>res/transition</code> directory. TransitionManager resources consist of
42 * the <code>transitionManager</code>tag name, containing one or more
43 * <code>transition</code> tags, each of which describe the relationship of
44 * that transition to the from/to scene information in that tag.
45 * For example, here is a resource file that declares several scene
46 * transitions:</p>
47 *
48 * {@sample development/samples/ApiDemos/res/transition/transitions_mgr.xml TransitionManager}
49 *
50 * <p>For each of the <code>fromScene</code> and <code>toScene</code> attributes,
51 * there is a reference to a standard XML layout file. This is equivalent to
52 * creating a scene from a layout in code by calling
53 * {@link Scene#getSceneForLayout(ViewGroup, int, Context)}. For the
54 * <code>transition</code> attribute, there is a reference to a resource
55 * file in the <code>res/transition</code> directory which describes that
56 * transition.</p>
57 *
58 * Information on XML resource descriptions for transitions can be found for
59 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
60 * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade},
61 * and {@link android.R.styleable#TransitionManager}.
Chet Haasefaebd8f2012-05-18 14:17:57 -070062 */
63public class TransitionManager {
64 // TODO: how to handle enter/exit?
65
Chet Haasec43524f2013-07-16 14:40:11 -070066 private static String LOG_TAG = "TransitionManager";
67
Chet Haased82c8ac2013-08-26 14:20:16 -070068 private static Transition sDefaultTransition = new AutoTransition();
Chet Haasefaebd8f2012-05-18 14:17:57 -070069
Chet Haase08735182013-06-04 10:44:40 -070070 ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
71 ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
72 new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
Chet Haase7660d122013-09-13 13:29:31 -070073 private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
74 sRunningTransitions =
75 new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>();
Chet Haase4f507232013-06-10 13:00:06 -070076 private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>();
Chet Haasefaebd8f2012-05-18 14:17:57 -070077
Chet Haase4f507232013-06-10 13:00:06 -070078
79 /**
Chet Haasefaebd8f2012-05-18 14:17:57 -070080 * Sets the transition to be used for any scene change for which no
81 * other transition is explicitly set. The initial value is
82 * an {@link AutoTransition} instance.
83 *
84 * @param transition The default transition to be used for scene changes.
Adam Powell1e9f3d82013-10-24 18:54:33 -070085 *
86 * @hide pending later changes
Chet Haasefaebd8f2012-05-18 14:17:57 -070087 */
88 public void setDefaultTransition(Transition transition) {
Chet Haased82c8ac2013-08-26 14:20:16 -070089 sDefaultTransition = transition;
Chet Haasefaebd8f2012-05-18 14:17:57 -070090 }
91
92 /**
93 * Gets the current default transition. The initial value is an {@link
94 * AutoTransition} instance.
95 *
96 * @return The current default transition.
97 * @see #setDefaultTransition(Transition)
Adam Powell1e9f3d82013-10-24 18:54:33 -070098 *
99 * @hide pending later changes
Chet Haasefaebd8f2012-05-18 14:17:57 -0700100 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700101 public static Transition getDefaultTransition() {
102 return sDefaultTransition;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700103 }
104
105 /**
106 * Sets a specific transition to occur when the given scene is entered.
107 *
108 * @param scene The scene which, when applied, will cause the given
109 * transition to run.
110 * @param transition The transition that will play when the given scene is
111 * entered. A value of null will result in the default behavior of
Adam Powell1e9f3d82013-10-24 18:54:33 -0700112 * using the default transition instead.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700113 */
114 public void setTransition(Scene scene, Transition transition) {
115 mSceneTransitions.put(scene, transition);
116 }
117
118 /**
119 * Sets a specific transition to occur when the given pair of scenes is
120 * exited/entered.
121 *
122 * @param fromScene The scene being exited when the given transition will
123 * be run
124 * @param toScene The scene being entered when the given transition will
125 * be run
126 * @param transition The transition that will play when the given scene is
127 * entered. A value of null will result in the default behavior of
Adam Powell1e9f3d82013-10-24 18:54:33 -0700128 * using the default transition instead.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700129 */
130 public void setTransition(Scene fromScene, Scene toScene, Transition transition) {
Chet Haase08735182013-06-04 10:44:40 -0700131 ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700132 if (sceneTransitionMap == null) {
Chet Haase08735182013-06-04 10:44:40 -0700133 sceneTransitionMap = new ArrayMap<Scene, Transition>();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700134 mScenePairTransitions.put(toScene, sceneTransitionMap);
135 }
136 sceneTransitionMap.put(fromScene, transition);
137 }
138
139 /**
140 * Returns the Transition for the given scene being entered. The result
141 * depends not only on the given scene, but also the scene which the
142 * {@link Scene#getSceneRoot() sceneRoot} of the Scene is currently in.
143 *
144 * @param scene The scene being entered
145 * @return The Transition to be used for the given scene change. If no
Adam Powell1e9f3d82013-10-24 18:54:33 -0700146 * Transition was specified for this scene change, the default transition
147 * will be used instead.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700148 */
149 private Transition getTransition(Scene scene) {
150 Transition transition = null;
151 ViewGroup sceneRoot = scene.getSceneRoot();
152 if (sceneRoot != null) {
153 // TODO: cached in Scene instead? long-term, cache in View itself
Chet Haased82c8ac2013-08-26 14:20:16 -0700154 Scene currScene = Scene.getCurrentScene(sceneRoot);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700155 if (currScene != null) {
Chet Haase08735182013-06-04 10:44:40 -0700156 ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(scene);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700157 if (sceneTransitionMap != null) {
158 transition = sceneTransitionMap.get(currScene);
159 if (transition != null) {
160 return transition;
161 }
162 }
163 }
164 }
165 transition = mSceneTransitions.get(scene);
Chet Haased82c8ac2013-08-26 14:20:16 -0700166 return (transition != null) ? transition : sDefaultTransition;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700167 }
168
169 /**
170 * This is where all of the work of a transition/scene-change is
171 * orchestrated. This method captures the start values for the given
172 * transition, exits the current Scene, enters the new scene, captures
173 * the end values for the transition, and finally plays the
174 * resulting values-populated transition.
175 *
176 * @param scene The scene being entered
177 * @param transition The transition to play for this scene change
178 */
Chet Haase6ebe3de2013-06-17 16:50:50 -0700179 private static void changeScene(Scene scene, Transition transition) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700180
181 final ViewGroup sceneRoot = scene.getSceneRoot();
182
Chet Haase6ebe3de2013-06-17 16:50:50 -0700183 Transition transitionClone = transition.clone();
184 transitionClone.setSceneRoot(sceneRoot);
185
Chet Haaseb7a7fc92013-09-20 16:33:08 -0700186 Scene oldScene = Scene.getCurrentScene(sceneRoot);
187 if (oldScene != null && oldScene.isCreatedFromLayoutResource()) {
188 transitionClone.setCanRemoveViews(true);
189 }
190
Chet Haase6ebe3de2013-06-17 16:50:50 -0700191 sceneChangeSetup(sceneRoot, transitionClone);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700192
193 scene.enter();
194
Chet Haase6ebe3de2013-06-17 16:50:50 -0700195 sceneChangeRunTransition(sceneRoot, transitionClone);
Chet Haase4f507232013-06-10 13:00:06 -0700196 }
197
Chet Haase199acdf2013-07-24 18:40:55 -0700198 private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() {
Chet Haase7660d122013-09-13 13:29:31 -0700199 WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>> runningTransitions =
Chet Haase199acdf2013-07-24 18:40:55 -0700200 sRunningTransitions.get();
Chet Haase7660d122013-09-13 13:29:31 -0700201 if (runningTransitions == null || runningTransitions.get() == null) {
202 ArrayMap<ViewGroup, ArrayList<Transition>> transitions =
203 new ArrayMap<ViewGroup, ArrayList<Transition>>();
204 runningTransitions = new WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>(
205 transitions);
Chet Haase199acdf2013-07-24 18:40:55 -0700206 sRunningTransitions.set(runningTransitions);
207 }
Chet Haase7660d122013-09-13 13:29:31 -0700208 return runningTransitions.get();
Chet Haase199acdf2013-07-24 18:40:55 -0700209 }
210
Chet Haase4f507232013-06-10 13:00:06 -0700211 private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
212 final Transition transition) {
Chet Haasedf32aa82013-10-21 17:19:37 -0700213 if (transition != null && sceneRoot != null) {
214 MultiListener listener = new MultiListener(transition, sceneRoot);
215 sceneRoot.addOnAttachStateChangeListener(listener);
216 sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700217 }
218 }
219
Chet Haasedf32aa82013-10-21 17:19:37 -0700220 /**
221 * This private utility class is used to listen for both OnPreDraw and
222 * OnAttachStateChange events. OnPreDraw events are the main ones we care
223 * about since that's what triggers the transition to take place.
224 * OnAttachStateChange events are also important in case the view is removed
225 * from the hierarchy before the OnPreDraw event takes place; it's used to
226 * clean up things since the OnPreDraw listener didn't get called in time.
227 */
228 private static class MultiListener implements ViewTreeObserver.OnPreDrawListener,
229 View.OnAttachStateChangeListener {
230
231 Transition mTransition;
232 ViewGroup mSceneRoot;
233
234 MultiListener(Transition transition, ViewGroup sceneRoot) {
235 mTransition = transition;
236 mSceneRoot = sceneRoot;
237 }
238
239 private void removeListeners() {
240 mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
241 mSceneRoot.removeOnAttachStateChangeListener(this);
242 }
243
244 @Override
245 public void onViewAttachedToWindow(View v) {
246 }
247
248 @Override
249 public void onViewDetachedFromWindow(View v) {
250 removeListeners();
251
252 sPendingTransitions.remove(mSceneRoot);
253 ArrayList<Transition> runningTransitions = getRunningTransitions().get(mSceneRoot);
254 if (runningTransitions != null && runningTransitions.size() > 0) {
255 for (Transition runningTransition : runningTransitions) {
256 runningTransition.resume();
257 }
258 }
259 mTransition.clearValues(true);
260 }
261
262 @Override
263 public boolean onPreDraw() {
264 removeListeners();
265 sPendingTransitions.remove(mSceneRoot);
266 // Add to running list, handle end to remove it
267 final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
268 getRunningTransitions();
269 ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot);
270 ArrayList<Transition> previousRunningTransitions = null;
271 if (currentTransitions == null) {
272 currentTransitions = new ArrayList<Transition>();
273 runningTransitions.put(mSceneRoot, currentTransitions);
274 } else if (currentTransitions.size() > 0) {
275 previousRunningTransitions = new ArrayList<Transition>(currentTransitions);
276 }
277 currentTransitions.add(mTransition);
278 mTransition.addListener(new Transition.TransitionListenerAdapter() {
279 @Override
280 public void onTransitionEnd(Transition transition) {
281 ArrayList<Transition> currentTransitions =
282 runningTransitions.get(mSceneRoot);
283 currentTransitions.remove(transition);
284 }
285 });
286 mTransition.captureValues(mSceneRoot, false);
287 if (previousRunningTransitions != null) {
288 for (Transition runningTransition : previousRunningTransitions) {
289 runningTransition.resume();
290 }
291 }
292 mTransition.playTransition(mSceneRoot);
293
294 return true;
295 }
296 };
297
Chet Haase4f507232013-06-10 13:00:06 -0700298 private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
299
Chet Haase4f507232013-06-10 13:00:06 -0700300 // Capture current values
Chet Haase199acdf2013-07-24 18:40:55 -0700301 ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
302
303 if (runningTransitions != null && runningTransitions.size() > 0) {
304 for (Transition runningTransition : runningTransitions) {
305 runningTransition.pause();
306 }
307 }
Chet Haasec81a8492013-07-12 12:54:38 -0700308
Chet Haase4f507232013-06-10 13:00:06 -0700309 if (transition != null) {
310 transition.captureValues(sceneRoot, true);
311 }
312
313 // Notify previous scene that it is being exited
Chet Haased82c8ac2013-08-26 14:20:16 -0700314 Scene previousScene = Scene.getCurrentScene(sceneRoot);
Chet Haase4f507232013-06-10 13:00:06 -0700315 if (previousScene != null) {
316 previousScene.exit();
317 }
318 }
319
Chet Haasefaebd8f2012-05-18 14:17:57 -0700320 /**
321 * Change to the given scene, using the
322 * appropriate transition for this particular scene change
323 * (as specified to the TransitionManager, or the default
324 * if no such transition exists).
325 *
326 * @param scene The Scene to change to
327 */
328 public void transitionTo(Scene scene) {
329 // Auto transition if there is no transition declared for the Scene, but there is
330 // a root or parent view
331 changeScene(scene, getTransition(scene));
332
333 }
334
335 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700336 * Convenience method to simply change to the given scene using
Chet Haasefaebd8f2012-05-18 14:17:57 -0700337 * the default transition for TransitionManager.
338 *
339 * @param scene The Scene to change to
340 */
341 public static void go(Scene scene) {
342 changeScene(scene, sDefaultTransition);
343 }
344
345 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700346 * Convenience method to simply change to the given scene using
Chet Haasefaebd8f2012-05-18 14:17:57 -0700347 * the given transition.
348 *
349 * <p>Passing in <code>null</code> for the transition parameter will
350 * result in the scene changing without any transition running, and is
351 * equivalent to calling {@link Scene#exit()} on the scene root's
Chet Haased82c8ac2013-08-26 14:20:16 -0700352 * current scene, followed by {@link Scene#enter()} on the scene
353 * specified by the <code>scene</code> parameter.</p>
Chet Haasefaebd8f2012-05-18 14:17:57 -0700354 *
355 * @param scene The Scene to change to
356 * @param transition The transition to use for this scene change. A
357 * value of null causes the scene change to happen with no transition.
358 */
359 public static void go(Scene scene, Transition transition) {
360 changeScene(scene, transition);
361 }
362
363 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700364 * Convenience method to animate, using the default transition,
365 * to a new scene defined by all changes within the given scene root between
366 * calling this method and the next rendering frame.
367 * Equivalent to calling {@link #beginDelayedTransition(ViewGroup, Transition)}
368 * with a value of <code>null</code> for the <code>transition</code> parameter.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700369 *
370 * @param sceneRoot The root of the View hierarchy to run the transition on.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700371 */
Chet Haased82c8ac2013-08-26 14:20:16 -0700372 public static void beginDelayedTransition(final ViewGroup sceneRoot) {
373 beginDelayedTransition(sceneRoot, null);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700374 }
Chet Haase4f507232013-06-10 13:00:06 -0700375
376 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700377 * Convenience method to animate to a new scene defined by all changes within
Chet Haase4f507232013-06-10 13:00:06 -0700378 * the given scene root between calling this method and the next rendering frame.
379 * Calling this method causes TransitionManager to capture current values in the
380 * scene root and then post a request to run a transition on the next frame.
381 * At that time, the new values in the scene root will be captured and changes
382 * will be animated. There is no need to create a Scene; it is implied by
383 * changes which take place between calling this method and the next frame when
384 * the transition begins.
385 *
386 * <p>Calling this method several times before the next frame (for example, if
387 * unrelated code also wants to make dynamic changes and run a transition on
388 * the same scene root), only the first call will trigger capturing values
389 * and exiting the current scene. Subsequent calls to the method with the
390 * same scene root during the same frame will be ignored.</p>
391 *
392 * <p>Passing in <code>null</code> for the transition parameter will
393 * cause the TransitionManager to use its default transition.</p>
394 *
395 * @param sceneRoot The root of the View hierarchy to run the transition on.
396 * @param transition The transition to use for this change. A
397 * value of null causes the TransitionManager to use the default transition.
398 */
399 public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
Chet Haase23c61f62013-09-14 11:28:46 -0700400 if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
401 if (Transition.DBG) {
402 Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
403 sceneRoot + ", " + transition);
404 }
405 sPendingTransitions.add(sceneRoot);
406 if (transition == null) {
407 transition = sDefaultTransition;
408 }
409 final Transition transitionClone = transition.clone();
410 sceneChangeSetup(sceneRoot, transitionClone);
411 Scene.setCurrentScene(sceneRoot, null);
412 sceneChangeRunTransition(sceneRoot, transitionClone);
413 }
Chet Haase4f507232013-06-10 13:00:06 -0700414 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700415}