TransitionManager.java revision cf68aad3164303df59b2a669d186a94533c9c743
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.View; 23import android.view.ViewGroup; 24import android.view.ViewTreeObserver; 25 26import java.lang.ref.WeakReference; 27import 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 */ 63public 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 private static final String[] EMPTY_STRINGS = new String[0]; 71 72 ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>(); 73 ArrayMap<Scene, Transition> mExitSceneTransitions = new ArrayMap<Scene, Transition>(); 74 ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions = 75 new ArrayMap<Scene, ArrayMap<Scene, Transition>>(); 76 private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>> 77 sRunningTransitions = 78 new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>(); 79 private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>(); 80 81 82 /** 83 * Sets the transition to be used for any scene change for which no 84 * other transition is explicitly set. The initial value is 85 * an {@link AutoTransition} instance. 86 * 87 * @param transition The default transition to be used for scene changes. 88 * 89 * @hide pending later changes 90 */ 91 public void setDefaultTransition(Transition transition) { 92 sDefaultTransition = transition; 93 } 94 95 /** 96 * Gets the current default transition. The initial value is an {@link 97 * AutoTransition} instance. 98 * 99 * @return The current default transition. 100 * @see #setDefaultTransition(Transition) 101 * 102 * @hide pending later changes 103 */ 104 public static Transition getDefaultTransition() { 105 return sDefaultTransition; 106 } 107 108 /** 109 * Sets a specific transition to occur when the given scene is entered. 110 * 111 * @param scene The scene which, when applied, will cause the given 112 * transition to run. 113 * @param transition The transition that will play when the given scene is 114 * entered. A value of null will result in the default behavior of 115 * using the default transition instead. 116 */ 117 public void setTransition(Scene scene, Transition transition) { 118 mSceneTransitions.put(scene, transition); 119 } 120 121 /** 122 * Sets a specific transition to occur when the given scene is exited. This 123 * has the lowest priority -- if a Scene-to-Scene transition or 124 * Scene enter transition can be applied, it will. 125 * 126 * @param scene The scene which, when exited, will cause the given 127 * transition to run. 128 * @param transition The transition that will play when the given scene is 129 * exited. A value of null will result in the default behavior of 130 * using the default transition instead. 131 */ 132 public void setExitTransition(Scene scene, Transition transition) { 133 mExitSceneTransitions.put(scene, transition); 134 } 135 136 /** 137 * Sets a specific transition to occur when the given pair of scenes is 138 * exited/entered. 139 * 140 * @param fromScene The scene being exited when the given transition will 141 * be run 142 * @param toScene The scene being entered when the given transition will 143 * be run 144 * @param transition The transition that will play when the given scene is 145 * entered. A value of null will result in the default behavior of 146 * using the default transition instead. 147 */ 148 public void setTransition(Scene fromScene, Scene toScene, Transition transition) { 149 ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene); 150 if (sceneTransitionMap == null) { 151 sceneTransitionMap = new ArrayMap<Scene, Transition>(); 152 mScenePairTransitions.put(toScene, sceneTransitionMap); 153 } 154 sceneTransitionMap.put(fromScene, transition); 155 } 156 157 /** 158 * Returns the Transition for the given scene being entered. The result 159 * depends not only on the given scene, but also the scene which the 160 * {@link Scene#getSceneRoot() sceneRoot} of the Scene is currently in. 161 * 162 * @param scene The scene being entered 163 * @return The Transition to be used for the given scene change. If no 164 * Transition was specified for this scene change, the default transition 165 * will be used instead. 166 */ 167 private Transition getTransition(Scene scene) { 168 Transition transition = null; 169 ViewGroup sceneRoot = scene.getSceneRoot(); 170 if (sceneRoot != null) { 171 // TODO: cached in Scene instead? long-term, cache in View itself 172 Scene currScene = Scene.getCurrentScene(sceneRoot); 173 if (currScene != null) { 174 ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(scene); 175 if (sceneTransitionMap != null) { 176 transition = sceneTransitionMap.get(currScene); 177 if (transition != null) { 178 return transition; 179 } 180 } 181 } 182 } 183 transition = mSceneTransitions.get(scene); 184 if (transition == null && sceneRoot != null) { 185 transition = mExitSceneTransitions.get(Scene.getCurrentScene(sceneRoot)); 186 } 187 return (transition != null) ? transition : sDefaultTransition; 188 } 189 190 /** 191 * This is where all of the work of a transition/scene-change is 192 * orchestrated. This method captures the start values for the given 193 * transition, exits the current Scene, enters the new scene, captures 194 * the end values for the transition, and finally plays the 195 * resulting values-populated transition. 196 * 197 * @param scene The scene being entered 198 * @param transition The transition to play for this scene change 199 */ 200 private static void changeScene(Scene scene, Transition transition) { 201 202 final ViewGroup sceneRoot = scene.getSceneRoot(); 203 204 Transition transitionClone = transition.clone(); 205 transitionClone.setSceneRoot(sceneRoot); 206 207 Scene oldScene = Scene.getCurrentScene(sceneRoot); 208 if (oldScene != null && oldScene.isCreatedFromLayoutResource()) { 209 transitionClone.setCanRemoveViews(true); 210 } 211 212 sceneChangeSetup(sceneRoot, transitionClone); 213 214 scene.enter(); 215 216 sceneChangeRunTransition(sceneRoot, transitionClone); 217 } 218 219 private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() { 220 WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>> runningTransitions = 221 sRunningTransitions.get(); 222 if (runningTransitions == null || runningTransitions.get() == null) { 223 ArrayMap<ViewGroup, ArrayList<Transition>> transitions = 224 new ArrayMap<ViewGroup, ArrayList<Transition>>(); 225 runningTransitions = new WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>( 226 transitions); 227 sRunningTransitions.set(runningTransitions); 228 } 229 return runningTransitions.get(); 230 } 231 232 private static void sceneChangeRunTransition(final ViewGroup sceneRoot, 233 final Transition transition) { 234 if (transition != null && sceneRoot != null) { 235 MultiListener listener = new MultiListener(transition, sceneRoot); 236 sceneRoot.addOnAttachStateChangeListener(listener); 237 sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener); 238 } 239 } 240 241 /** 242 * Retrieve the transition to a target defined scene if one has been 243 * associated with this TransitionManager. 244 * 245 * @param toScene Target scene that this transition will move to 246 * @return Transition corresponding to the given toScene or null 247 * if no association exists in this TransitionManager 248 * 249 * @see #setTransition(Scene, Transition) 250 * @hide 251 */ 252 public Transition getEnterTransition(Scene toScene) { 253 return mSceneTransitions.get(toScene); 254 } 255 256 /** 257 * Retrieve the transition from a defined scene to a target named scene if one has been 258 * associated with this TransitionManager. 259 * 260 * @param fromScene Scene that this transition starts from 261 * @return Transition corresponding to the given fromScene or null 262 * if no association exists in this TransitionManager 263 * @hide 264 */ 265 public Transition getExitTransition(Scene fromScene) { 266 return mExitSceneTransitions.get(fromScene); 267 } 268 269 /** 270 * This private utility class is used to listen for both OnPreDraw and 271 * OnAttachStateChange events. OnPreDraw events are the main ones we care 272 * about since that's what triggers the transition to take place. 273 * OnAttachStateChange events are also important in case the view is removed 274 * from the hierarchy before the OnPreDraw event takes place; it's used to 275 * clean up things since the OnPreDraw listener didn't get called in time. 276 */ 277 private static class MultiListener implements ViewTreeObserver.OnPreDrawListener, 278 View.OnAttachStateChangeListener { 279 280 Transition mTransition; 281 ViewGroup mSceneRoot; 282 283 MultiListener(Transition transition, ViewGroup sceneRoot) { 284 mTransition = transition; 285 mSceneRoot = sceneRoot; 286 } 287 288 private void removeListeners() { 289 mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); 290 mSceneRoot.removeOnAttachStateChangeListener(this); 291 } 292 293 @Override 294 public void onViewAttachedToWindow(View v) { 295 } 296 297 @Override 298 public void onViewDetachedFromWindow(View v) { 299 removeListeners(); 300 301 sPendingTransitions.remove(mSceneRoot); 302 ArrayList<Transition> runningTransitions = getRunningTransitions().get(mSceneRoot); 303 if (runningTransitions != null && runningTransitions.size() > 0) { 304 for (Transition runningTransition : runningTransitions) { 305 runningTransition.resume(mSceneRoot); 306 } 307 } 308 mTransition.clearValues(true); 309 } 310 311 @Override 312 public boolean onPreDraw() { 313 removeListeners(); 314 sPendingTransitions.remove(mSceneRoot); 315 // Add to running list, handle end to remove it 316 final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions = 317 getRunningTransitions(); 318 ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot); 319 ArrayList<Transition> previousRunningTransitions = null; 320 if (currentTransitions == null) { 321 currentTransitions = new ArrayList<Transition>(); 322 runningTransitions.put(mSceneRoot, currentTransitions); 323 } else if (currentTransitions.size() > 0) { 324 previousRunningTransitions = new ArrayList<Transition>(currentTransitions); 325 } 326 currentTransitions.add(mTransition); 327 mTransition.addListener(new Transition.TransitionListenerAdapter() { 328 @Override 329 public void onTransitionEnd(Transition transition) { 330 ArrayList<Transition> currentTransitions = 331 runningTransitions.get(mSceneRoot); 332 currentTransitions.remove(transition); 333 } 334 }); 335 mTransition.captureValues(mSceneRoot, false); 336 if (previousRunningTransitions != null) { 337 for (Transition runningTransition : previousRunningTransitions) { 338 runningTransition.resume(mSceneRoot); 339 } 340 } 341 mTransition.playTransition(mSceneRoot); 342 343 return true; 344 } 345 }; 346 347 private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) { 348 349 // Capture current values 350 ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot); 351 352 if (runningTransitions != null && runningTransitions.size() > 0) { 353 for (Transition runningTransition : runningTransitions) { 354 runningTransition.pause(sceneRoot); 355 } 356 } 357 358 if (transition != null) { 359 transition.captureValues(sceneRoot, true); 360 } 361 362 // Notify previous scene that it is being exited 363 Scene previousScene = Scene.getCurrentScene(sceneRoot); 364 if (previousScene != null) { 365 previousScene.exit(); 366 } 367 } 368 369 /** 370 * Change to the given scene, using the 371 * appropriate transition for this particular scene change 372 * (as specified to the TransitionManager, or the default 373 * if no such transition exists). 374 * 375 * @param scene The Scene to change to 376 */ 377 public void transitionTo(Scene scene) { 378 // Auto transition if there is no transition declared for the Scene, but there is 379 // a root or parent view 380 changeScene(scene, getTransition(scene)); 381 } 382 383 /** 384 * Convenience method to simply change to the given scene using 385 * the default transition for TransitionManager. 386 * 387 * @param scene The Scene to change to 388 */ 389 public static void go(Scene scene) { 390 changeScene(scene, sDefaultTransition); 391 } 392 393 /** 394 * Convenience method to simply change to the given scene using 395 * the given transition. 396 * 397 * <p>Passing in <code>null</code> for the transition parameter will 398 * result in the scene changing without any transition running, and is 399 * equivalent to calling {@link Scene#exit()} on the scene root's 400 * current scene, followed by {@link Scene#enter()} on the scene 401 * specified by the <code>scene</code> parameter.</p> 402 * 403 * @param scene The Scene to change to 404 * @param transition The transition to use for this scene change. A 405 * value of null causes the scene change to happen with no transition. 406 */ 407 public static void go(Scene scene, Transition transition) { 408 changeScene(scene, transition); 409 } 410 411 /** 412 * Convenience method to animate, using the default transition, 413 * to a new scene defined by all changes within the given scene root between 414 * calling this method and the next rendering frame. 415 * Equivalent to calling {@link #beginDelayedTransition(ViewGroup, Transition)} 416 * with a value of <code>null</code> for the <code>transition</code> parameter. 417 * 418 * @param sceneRoot The root of the View hierarchy to run the transition on. 419 */ 420 public static void beginDelayedTransition(final ViewGroup sceneRoot) { 421 beginDelayedTransition(sceneRoot, null); 422 } 423 424 /** 425 * Convenience method to animate to a new scene defined by all changes within 426 * the given scene root between calling this method and the next rendering frame. 427 * Calling this method causes TransitionManager to capture current values in the 428 * scene root and then post a request to run a transition on the next frame. 429 * At that time, the new values in the scene root will be captured and changes 430 * will be animated. There is no need to create a Scene; it is implied by 431 * changes which take place between calling this method and the next frame when 432 * the transition begins. 433 * 434 * <p>Calling this method several times before the next frame (for example, if 435 * unrelated code also wants to make dynamic changes and run a transition on 436 * the same scene root), only the first call will trigger capturing values 437 * and exiting the current scene. Subsequent calls to the method with the 438 * same scene root during the same frame will be ignored.</p> 439 * 440 * <p>Passing in <code>null</code> for the transition parameter will 441 * cause the TransitionManager to use its default transition.</p> 442 * 443 * @param sceneRoot The root of the View hierarchy to run the transition on. 444 * @param transition The transition to use for this change. A 445 * value of null causes the TransitionManager to use the default transition. 446 */ 447 public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) { 448 if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) { 449 if (Transition.DBG) { 450 Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " + 451 sceneRoot + ", " + transition); 452 } 453 sPendingTransitions.add(sceneRoot); 454 if (transition == null) { 455 transition = sDefaultTransition; 456 } 457 final Transition transitionClone = transition.clone(); 458 sceneChangeSetup(sceneRoot, transitionClone); 459 Scene.setCurrentScene(sceneRoot, null); 460 sceneChangeRunTransition(sceneRoot, transitionClone); 461 } 462 } 463} 464