• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.transition;
18 
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;
25 
26 import java.lang.ref.WeakReference;
27 import java.util.ArrayList;
28 
29 /**
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.
39  *
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}.
62  */
63 public class TransitionManager {
64     // TODO: how to handle enter/exit?
65 
66     private static String LOG_TAG = "TransitionManager";
67 
68     private static Transition sDefaultTransition = new AutoTransition();
69 
70     ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
71     ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
72             new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
73     private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
74             sRunningTransitions =
75             new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>();
76     private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>();
77 
78 
79     /**
80      * 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.
85      *
86      * @hide pending later changes
87      */
setDefaultTransition(Transition transition)88     public void setDefaultTransition(Transition transition) {
89         sDefaultTransition = transition;
90     }
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)
98      *
99      * @hide pending later changes
100      */
getDefaultTransition()101     public static Transition getDefaultTransition() {
102         return sDefaultTransition;
103     }
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
112      * using the default transition instead.
113      */
setTransition(Scene scene, Transition transition)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
128      * using the default transition instead.
129      */
setTransition(Scene fromScene, Scene toScene, Transition transition)130     public void setTransition(Scene fromScene, Scene toScene, Transition transition) {
131         ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene);
132         if (sceneTransitionMap == null) {
133             sceneTransitionMap = new ArrayMap<Scene, Transition>();
134             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
146      * Transition was specified for this scene change, the default transition
147      * will be used instead.
148      */
getTransition(Scene scene)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
154             Scene currScene = Scene.getCurrentScene(sceneRoot);
155             if (currScene != null) {
156                 ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(scene);
157                 if (sceneTransitionMap != null) {
158                     transition = sceneTransitionMap.get(currScene);
159                     if (transition != null) {
160                         return transition;
161                     }
162                 }
163             }
164         }
165         transition = mSceneTransitions.get(scene);
166         return (transition != null) ? transition : sDefaultTransition;
167     }
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      */
changeScene(Scene scene, Transition transition)179     private static void changeScene(Scene scene, Transition transition) {
180 
181         final ViewGroup sceneRoot = scene.getSceneRoot();
182 
183         Transition transitionClone = transition.clone();
184         transitionClone.setSceneRoot(sceneRoot);
185 
186         Scene oldScene = Scene.getCurrentScene(sceneRoot);
187         if (oldScene != null && oldScene.isCreatedFromLayoutResource()) {
188             transitionClone.setCanRemoveViews(true);
189         }
190 
191         sceneChangeSetup(sceneRoot, transitionClone);
192 
193         scene.enter();
194 
195         sceneChangeRunTransition(sceneRoot, transitionClone);
196     }
197 
getRunningTransitions()198     private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() {
199         WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>> runningTransitions =
200                 sRunningTransitions.get();
201         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);
206             sRunningTransitions.set(runningTransitions);
207         }
208         return runningTransitions.get();
209     }
210 
sceneChangeRunTransition(final ViewGroup sceneRoot, final Transition transition)211     private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
212             final Transition transition) {
213         if (transition != null && sceneRoot != null) {
214             MultiListener listener = new MultiListener(transition, sceneRoot);
215             sceneRoot.addOnAttachStateChangeListener(listener);
216             sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
217         }
218     }
219 
220     /**
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 
MultiListener(Transition transition, ViewGroup sceneRoot)234         MultiListener(Transition transition, ViewGroup sceneRoot) {
235             mTransition = transition;
236             mSceneRoot = sceneRoot;
237         }
238 
removeListeners()239         private void removeListeners() {
240             mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
241             mSceneRoot.removeOnAttachStateChangeListener(this);
242         }
243 
244         @Override
onViewAttachedToWindow(View v)245         public void onViewAttachedToWindow(View v) {
246         }
247 
248         @Override
onViewDetachedFromWindow(View v)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
onPreDraw()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 
sceneChangeSetup(ViewGroup sceneRoot, Transition transition)298     private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
299 
300         // Capture current values
301         ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
302 
303         if (runningTransitions != null && runningTransitions.size() > 0) {
304             for (Transition runningTransition : runningTransitions) {
305                 runningTransition.pause();
306             }
307         }
308 
309         if (transition != null) {
310             transition.captureValues(sceneRoot, true);
311         }
312 
313         // Notify previous scene that it is being exited
314         Scene previousScene = Scene.getCurrentScene(sceneRoot);
315         if (previousScene != null) {
316             previousScene.exit();
317         }
318     }
319 
320     /**
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      */
transitionTo(Scene scene)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     /**
336      * Convenience method to simply change to the given scene using
337      * the default transition for TransitionManager.
338      *
339      * @param scene The Scene to change to
340      */
go(Scene scene)341     public static void go(Scene scene) {
342         changeScene(scene, sDefaultTransition);
343     }
344 
345     /**
346      * Convenience method to simply change to the given scene using
347      * 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
352      * current scene, followed by {@link Scene#enter()} on the scene
353      * specified by the <code>scene</code> parameter.</p>
354      *
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      */
go(Scene scene, Transition transition)359     public static void go(Scene scene, Transition transition) {
360         changeScene(scene, transition);
361     }
362 
363     /**
364      * 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.
369      *
370      * @param sceneRoot The root of the View hierarchy to run the transition on.
371      */
beginDelayedTransition(final ViewGroup sceneRoot)372     public static void beginDelayedTransition(final ViewGroup sceneRoot) {
373         beginDelayedTransition(sceneRoot, null);
374     }
375 
376     /**
377      * Convenience method to animate to a new scene defined by all changes within
378      * 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      */
beginDelayedTransition(final ViewGroup sceneRoot, Transition transition)399     public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
400         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         }
414     }
415 }
416