2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.transition;
19 import android.content.Context;
20 import android.util.ArrayMap;
21 import android.util.Log;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.view.ViewTreeObserver;
26 import java.lang.ref.WeakReference;
27 import java.util.ArrayList;
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.
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
48 * {@sample development/samples/ApiDemos/res/transition/transitions_mgr.xml TransitionManager}
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
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}.
63 public class TransitionManager {
64 // TODO: how to handle enter/exit?
66 private static String LOG_TAG = "TransitionManager";
68 private static Transition sDefaultTransition = new AutoTransition();
70 private static final String[] EMPTY_STRINGS = new String[0];
72 ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
73 ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
74 new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
75 private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
77 new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>();
78 private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>();
82 * Sets the transition to be used for any scene change for which no
83 * other transition is explicitly set. The initial value is
84 * an {@link AutoTransition} instance.
86 * @param transition The default transition to be used for scene changes.
88 * @hide pending later changes
90 public void setDefaultTransition(Transition transition) {
91 sDefaultTransition = transition;
95 * Gets the current default transition. The initial value is an {@link
96 * AutoTransition} instance.
98 * @return The current default transition.
99 * @see #setDefaultTransition(Transition)
101 * @hide pending later changes
103 public static Transition getDefaultTransition() {
104 return sDefaultTransition;
108 * Sets a specific transition to occur when the given scene is entered.
110 * @param scene The scene which, when applied, will cause the given
112 * @param transition The transition that will play when the given scene is
113 * entered. A value of null will result in the default behavior of
114 * using the default transition instead.
116 public void setTransition(Scene scene, Transition transition) {
117 mSceneTransitions.put(scene, transition);
121 * Sets a specific transition to occur when the given pair of scenes is
124 * @param fromScene The scene being exited when the given transition will
126 * @param toScene The scene being entered when the given transition will
128 * @param transition The transition that will play when the given scene is
129 * entered. A value of null will result in the default behavior of
130 * using the default transition instead.
132 public void setTransition(Scene fromScene, Scene toScene, Transition transition) {
133 ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene);
134 if (sceneTransitionMap == null) {
135 sceneTransitionMap = new ArrayMap<Scene, Transition>();
136 mScenePairTransitions.put(toScene, sceneTransitionMap);
138 sceneTransitionMap.put(fromScene, transition);
142 * Returns the Transition for the given scene being entered. The result
143 * depends not only on the given scene, but also the scene which the
144 * {@link Scene#getSceneRoot() sceneRoot} of the Scene is currently in.
146 * @param scene The scene being entered
147 * @return The Transition to be used for the given scene change. If no
148 * Transition was specified for this scene change, the default transition
149 * will be used instead.
151 private Transition getTransition(Scene scene) {
152 Transition transition = null;
153 ViewGroup sceneRoot = scene.getSceneRoot();
154 if (sceneRoot != null) {
155 // TODO: cached in Scene instead? long-term, cache in View itself
156 Scene currScene = Scene.getCurrentScene(sceneRoot);
157 if (currScene != null) {
158 ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(scene);
159 if (sceneTransitionMap != null) {
160 transition = sceneTransitionMap.get(currScene);
161 if (transition != null) {
167 transition = mSceneTransitions.get(scene);
168 return (transition != null) ? transition : sDefaultTransition;
172 * This is where all of the work of a transition/scene-change is
173 * orchestrated. This method captures the start values for the given
174 * transition, exits the current Scene, enters the new scene, captures
175 * the end values for the transition, and finally plays the
176 * resulting values-populated transition.
178 * @param scene The scene being entered
179 * @param transition The transition to play for this scene change
181 private static void changeScene(Scene scene, Transition transition) {
183 final ViewGroup sceneRoot = scene.getSceneRoot();
184 if (!sPendingTransitions.contains(sceneRoot)) {
185 sPendingTransitions.add(sceneRoot);
187 Transition transitionClone = null;
188 if (transition != null) {
189 transitionClone = transition.clone();
190 transitionClone.setSceneRoot(sceneRoot);
193 Scene oldScene = Scene.getCurrentScene(sceneRoot);
194 if (oldScene != null && transitionClone != null &&
195 oldScene.isCreatedFromLayoutResource()) {
196 transitionClone.setCanRemoveViews(true);
199 sceneChangeSetup(sceneRoot, transitionClone);
203 sceneChangeRunTransition(sceneRoot, transitionClone);
207 private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() {
208 WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>> runningTransitions =
209 sRunningTransitions.get();
210 if (runningTransitions == null || runningTransitions.get() == null) {
211 ArrayMap<ViewGroup, ArrayList<Transition>> transitions =
212 new ArrayMap<ViewGroup, ArrayList<Transition>>();
213 runningTransitions = new WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>(
215 sRunningTransitions.set(runningTransitions);
217 return runningTransitions.get();
220 private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
221 final Transition transition) {
222 if (transition != null && sceneRoot != null) {
223 MultiListener listener = new MultiListener(transition, sceneRoot);
224 sceneRoot.addOnAttachStateChangeListener(listener);
225 sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
230 * This private utility class is used to listen for both OnPreDraw and
231 * OnAttachStateChange events. OnPreDraw events are the main ones we care
232 * about since that's what triggers the transition to take place.
233 * OnAttachStateChange events are also important in case the view is removed
234 * from the hierarchy before the OnPreDraw event takes place; it's used to
235 * clean up things since the OnPreDraw listener didn't get called in time.
237 private static class MultiListener implements ViewTreeObserver.OnPreDrawListener,
238 View.OnAttachStateChangeListener {
240 Transition mTransition;
241 ViewGroup mSceneRoot;
243 MultiListener(Transition transition, ViewGroup sceneRoot) {
244 mTransition = transition;
245 mSceneRoot = sceneRoot;
248 private void removeListeners() {
249 mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
250 mSceneRoot.removeOnAttachStateChangeListener(this);
254 public void onViewAttachedToWindow(View v) {
258 public void onViewDetachedFromWindow(View v) {
261 sPendingTransitions.remove(mSceneRoot);
262 ArrayList<Transition> runningTransitions = getRunningTransitions().get(mSceneRoot);
263 if (runningTransitions != null && runningTransitions.size() > 0) {
264 for (Transition runningTransition : runningTransitions) {
265 runningTransition.resume(mSceneRoot);
268 mTransition.clearValues(true);
272 public boolean onPreDraw() {
275 // Don't start the transition if it's no longer pending.
276 if (!sPendingTransitions.remove(mSceneRoot)) {
280 // Add to running list, handle end to remove it
281 final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
282 getRunningTransitions();
283 ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot);
284 ArrayList<Transition> previousRunningTransitions = null;
285 if (currentTransitions == null) {
286 currentTransitions = new ArrayList<Transition>();
287 runningTransitions.put(mSceneRoot, currentTransitions);
288 } else if (currentTransitions.size() > 0) {
289 previousRunningTransitions = new ArrayList<Transition>(currentTransitions);
291 currentTransitions.add(mTransition);
292 mTransition.addListener(new Transition.TransitionListenerAdapter() {
294 public void onTransitionEnd(Transition transition) {
295 ArrayList<Transition> currentTransitions =
296 runningTransitions.get(mSceneRoot);
297 currentTransitions.remove(transition);
300 mTransition.captureValues(mSceneRoot, false);
301 if (previousRunningTransitions != null) {
302 for (Transition runningTransition : previousRunningTransitions) {
303 runningTransition.resume(mSceneRoot);
306 mTransition.playTransition(mSceneRoot);
312 private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
314 // Capture current values
315 ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
317 if (runningTransitions != null && runningTransitions.size() > 0) {
318 for (Transition runningTransition : runningTransitions) {
319 runningTransition.pause(sceneRoot);
323 if (transition != null) {
324 transition.captureValues(sceneRoot, true);
327 // Notify previous scene that it is being exited
328 Scene previousScene = Scene.getCurrentScene(sceneRoot);
329 if (previousScene != null) {
330 previousScene.exit();
335 * Change to the given scene, using the
336 * appropriate transition for this particular scene change
337 * (as specified to the TransitionManager, or the default
338 * if no such transition exists).
340 * @param scene The Scene to change to
342 public void transitionTo(Scene scene) {
343 // Auto transition if there is no transition declared for the Scene, but there is
344 // a root or parent view
345 changeScene(scene, getTransition(scene));
349 * Convenience method to simply change to the given scene using
350 * the default transition for TransitionManager.
352 * @param scene The Scene to change to
354 public static void go(Scene scene) {
355 changeScene(scene, sDefaultTransition);
359 * Convenience method to simply change to the given scene using
360 * the given transition.
362 * <p>Passing in <code>null</code> for the transition parameter will
363 * result in the scene changing without any transition running, and is
364 * equivalent to calling {@link Scene#exit()} on the scene root's
365 * current scene, followed by {@link Scene#enter()} on the scene
366 * specified by the <code>scene</code> parameter.</p>
368 * @param scene The Scene to change to
369 * @param transition The transition to use for this scene change. A
370 * value of null causes the scene change to happen with no transition.
372 public static void go(Scene scene, Transition transition) {
373 changeScene(scene, transition);
377 * Convenience method to animate, using the default transition,
378 * to a new scene defined by all changes within the given scene root between
379 * calling this method and the next rendering frame.
380 * Equivalent to calling {@link #beginDelayedTransition(ViewGroup, Transition)}
381 * with a value of <code>null</code> for the <code>transition</code> parameter.
383 * @param sceneRoot The root of the View hierarchy to run the transition on.
385 public static void beginDelayedTransition(final ViewGroup sceneRoot) {
386 beginDelayedTransition(sceneRoot, null);
390 * Convenience method to animate to a new scene defined by all changes within
391 * the given scene root between calling this method and the next rendering frame.
392 * Calling this method causes TransitionManager to capture current values in the
393 * scene root and then post a request to run a transition on the next frame.
394 * At that time, the new values in the scene root will be captured and changes
395 * will be animated. There is no need to create a Scene; it is implied by
396 * changes which take place between calling this method and the next frame when
397 * the transition begins.
399 * <p>Calling this method several times before the next frame (for example, if
400 * unrelated code also wants to make dynamic changes and run a transition on
401 * the same scene root), only the first call will trigger capturing values
402 * and exiting the current scene. Subsequent calls to the method with the
403 * same scene root during the same frame will be ignored.</p>
405 * <p>Passing in <code>null</code> for the transition parameter will
406 * cause the TransitionManager to use its default transition.</p>
408 * @param sceneRoot The root of the View hierarchy to run the transition on.
409 * @param transition The transition to use for this change. A
410 * value of null causes the TransitionManager to use the default transition.
412 public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
413 if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
414 if (Transition.DBG) {
415 Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
416 sceneRoot + ", " + transition);
418 sPendingTransitions.add(sceneRoot);
419 if (transition == null) {
420 transition = sDefaultTransition;
422 final Transition transitionClone = transition.clone();
423 sceneChangeSetup(sceneRoot, transitionClone);
424 Scene.setCurrentScene(sceneRoot, null);
425 sceneChangeRunTransition(sceneRoot, transitionClone);
430 * Ends all pending and ongoing transitions on the specified scene root.
432 * @param sceneRoot The root of the View hierarchy to end transitions on.
434 public static void endTransitions(final ViewGroup sceneRoot) {
435 sPendingTransitions.remove(sceneRoot);
437 final ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
438 if (runningTransitions != null && !runningTransitions.isEmpty()) {
439 // Make a copy in case this is called by an onTransitionEnd listener
440 ArrayList<Transition> copy = new ArrayList(runningTransitions);
441 for (int i = copy.size() - 1; i >= 0; i--) {
442 final Transition transition = copy.get(i);