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