TransitionManagerPort.java revision 761ea02d06e2181c6ba5f0b413bc5068bfb2733c
1/* 2 * Copyright (C) 2016 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.support.transition; 18 19import android.support.v4.util.ArrayMap; 20import android.support.v4.view.ViewCompat; 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 29class TransitionManagerPort { 30 // TODO: how to handle enter/exit? 31 32 private static final String[] EMPTY_STRINGS = new String[0]; 33 34 private static String LOG_TAG = "TransitionManager"; 35 36 private static TransitionPort sDefaultTransition = new AutoTransitionPort(); 37 38 private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<TransitionPort>>>> 39 sRunningTransitions = new ThreadLocal<>(); 40 41 static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<>(); 42 43 ArrayMap<ScenePort, TransitionPort> mSceneTransitions = new ArrayMap<>(); 44 45 ArrayMap<ScenePort, ArrayMap<ScenePort, TransitionPort>> mScenePairTransitions = 46 new ArrayMap<>(); 47 48 ArrayMap<ScenePort, ArrayMap<String, TransitionPort>> mSceneNameTransitions = new ArrayMap<>(); 49 50 ArrayMap<String, ArrayMap<ScenePort, TransitionPort>> mNameSceneTransitions = new ArrayMap<>(); 51 52 /** 53 * Gets the current default transition. The initial value is an {@link 54 * AutoTransition} instance. 55 * 56 * @return The current default transition. 57 * @hide pending later changes 58 * @see #setDefaultTransition(TransitionPort) 59 */ 60 public static TransitionPort getDefaultTransition() { 61 return sDefaultTransition; 62 } 63 64 /** 65 * Sets the transition to be used for any scene change for which no 66 * other transition is explicitly set. The initial value is 67 * an {@link AutoTransition} instance. 68 * 69 * @param transition The default transition to be used for scene changes. 70 * @hide pending later changes 71 */ 72 public void setDefaultTransition(TransitionPort transition) { 73 sDefaultTransition = transition; 74 } 75 76 /** 77 * This is where all of the work of a transition/scene-change is 78 * orchestrated. This method captures the start values for the given 79 * transition, exits the current Scene, enters the new scene, captures 80 * the end values for the transition, and finally plays the 81 * resulting values-populated transition. 82 * 83 * @param scene The scene being entered 84 * @param transition The transition to play for this scene change 85 */ 86 private static void changeScene(ScenePort scene, TransitionPort transition) { 87 88 final ViewGroup sceneRoot = scene.getSceneRoot(); 89 90 TransitionPort transitionClone = null; 91 if (transition != null) { 92 transitionClone = transition.clone(); 93 transitionClone.setSceneRoot(sceneRoot); 94 } 95 96 ScenePort oldScene = ScenePort.getCurrentScene(sceneRoot); 97 if (oldScene != null && oldScene.isCreatedFromLayoutResource()) { 98 transitionClone.setCanRemoveViews(true); 99 } 100 101 sceneChangeSetup(sceneRoot, transitionClone); 102 103 scene.enter(); 104 105 sceneChangeRunTransition(sceneRoot, transitionClone); 106 } 107 108 static ArrayMap<ViewGroup, ArrayList<TransitionPort>> getRunningTransitions() { 109 WeakReference<ArrayMap<ViewGroup, ArrayList<TransitionPort>>> runningTransitions = 110 sRunningTransitions.get(); 111 if (runningTransitions == null || runningTransitions.get() == null) { 112 ArrayMap<ViewGroup, ArrayList<TransitionPort>> transitions = new ArrayMap<>(); 113 runningTransitions = new WeakReference<>(transitions); 114 sRunningTransitions.set(runningTransitions); 115 } 116 return runningTransitions.get(); 117 } 118 119 private static void sceneChangeRunTransition(final ViewGroup sceneRoot, 120 final TransitionPort transition) { 121 if (transition != null && sceneRoot != null) { 122 MultiListener listener = new MultiListener(transition, sceneRoot); 123 sceneRoot.addOnAttachStateChangeListener(listener); 124 sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener); 125 } 126 } 127 128 private static void sceneChangeSetup(ViewGroup sceneRoot, TransitionPort transition) { 129 130 // Capture current values 131 ArrayList<TransitionPort> runningTransitions = getRunningTransitions().get(sceneRoot); 132 133 if (runningTransitions != null && runningTransitions.size() > 0) { 134 for (TransitionPort runningTransition : runningTransitions) { 135 runningTransition.pause(sceneRoot); 136 } 137 } 138 139 if (transition != null) { 140 transition.captureValues(sceneRoot, true); 141 } 142 143 // Notify previous scene that it is being exited 144 ScenePort previousScene = ScenePort.getCurrentScene(sceneRoot); 145 if (previousScene != null) { 146 previousScene.exit(); 147 } 148 } 149 150 public static void go(ScenePort scene) { 151 changeScene(scene, sDefaultTransition); 152 } 153 154 public static void go(ScenePort scene, TransitionPort transition) { 155 changeScene(scene, transition); 156 } 157 158 public static void beginDelayedTransition(final ViewGroup sceneRoot) { 159 beginDelayedTransition(sceneRoot, null); 160 } 161 162 public static void beginDelayedTransition(final ViewGroup sceneRoot, 163 TransitionPort transition) { 164 if (!sPendingTransitions.contains(sceneRoot) && ViewCompat.isLaidOut(sceneRoot)) { 165 if (TransitionPort.DBG) { 166 Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " + 167 sceneRoot + ", " + transition); 168 } 169 sPendingTransitions.add(sceneRoot); 170 if (transition == null) { 171 transition = sDefaultTransition; 172 } 173 final TransitionPort transitionClone = transition.clone(); 174 sceneChangeSetup(sceneRoot, transitionClone); 175 ScenePort.setCurrentScene(sceneRoot, null); 176 sceneChangeRunTransition(sceneRoot, transitionClone); 177 } 178 } 179 180 public void setTransition(ScenePort scene, TransitionPort transition) { 181 mSceneTransitions.put(scene, transition); 182 } 183 184 public void setTransition(ScenePort fromScene, ScenePort toScene, TransitionPort transition) { 185 ArrayMap<ScenePort, TransitionPort> sceneTransitionMap = mScenePairTransitions.get(toScene); 186 if (sceneTransitionMap == null) { 187 sceneTransitionMap = new ArrayMap<>(); 188 mScenePairTransitions.put(toScene, sceneTransitionMap); 189 } 190 sceneTransitionMap.put(fromScene, transition); 191 } 192 193 /** 194 * Returns the Transition for the given scene being entered. The result 195 * depends not only on the given scene, but also the scene which the 196 * {@link ScenePort#getSceneRoot() sceneRoot} of the Scene is currently in. 197 * 198 * @param scene The scene being entered 199 * @return The Transition to be used for the given scene change. If no 200 * Transition was specified for this scene change, the default transition 201 * will be used instead. 202 */ 203 private TransitionPort getTransition(ScenePort scene) { 204 TransitionPort transition; 205 ViewGroup sceneRoot = scene.getSceneRoot(); 206 if (sceneRoot != null) { 207 // TODO: cached in Scene instead? long-term, cache in View itself 208 ScenePort currScene = ScenePort.getCurrentScene(sceneRoot); 209 if (currScene != null) { 210 ArrayMap<ScenePort, TransitionPort> sceneTransitionMap = mScenePairTransitions 211 .get(scene); 212 if (sceneTransitionMap != null) { 213 transition = sceneTransitionMap.get(currScene); 214 if (transition != null) { 215 return transition; 216 } 217 } 218 } 219 } 220 transition = mSceneTransitions.get(scene); 221 return (transition != null) ? transition : sDefaultTransition; 222 } 223 224 /** 225 * Retrieve the transition from a named scene to a target defined scene if one has been 226 * associated with this TransitionManager. 227 * 228 * <p>A named scene is an indirect link for a transition. Fundamentally a named 229 * scene represents a potentially arbitrary intersection point of two otherwise independent 230 * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO" 231 * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y. 232 * In this way applications may define an API for more sophisticated transitions between 233 * caller and called activities very similar to the way that <code>Intent</code> extras 234 * define APIs for arguments and data propagation between activities.</p> 235 * 236 * @param fromName Named scene that this transition corresponds to 237 * @param toScene Target scene that this transition will move to 238 * @return Transition corresponding to the given fromName and toScene or null 239 * if no association exists in this TransitionManager 240 * @see #setTransition(String, ScenePort, TransitionPort) 241 */ 242 public TransitionPort getNamedTransition(String fromName, ScenePort toScene) { 243 ArrayMap<ScenePort, TransitionPort> m = mNameSceneTransitions.get(fromName); 244 if (m != null) { 245 return m.get(toScene); 246 } 247 return null; 248 } 249 250 ; 251 252 /** 253 * Retrieve the transition from a defined scene to a target named scene if one has been 254 * associated with this TransitionManager. 255 * 256 * <p>A named scene is an indirect link for a transition. Fundamentally a named 257 * scene represents a potentially arbitrary intersection point of two otherwise independent 258 * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO" 259 * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y. 260 * In this way applications may define an API for more sophisticated transitions between 261 * caller and called activities very similar to the way that <code>Intent</code> extras 262 * define APIs for arguments and data propagation between activities.</p> 263 * 264 * @param fromScene Scene that this transition starts from 265 * @param toName Name of the target scene 266 * @return Transition corresponding to the given fromScene and toName or null 267 * if no association exists in this TransitionManager 268 */ 269 public TransitionPort getNamedTransition(ScenePort fromScene, String toName) { 270 ArrayMap<String, TransitionPort> m = mSceneNameTransitions.get(fromScene); 271 if (m != null) { 272 return m.get(toName); 273 } 274 return null; 275 } 276 277 /** 278 * Retrieve the supported target named scenes when transitioning away from the given scene. 279 * 280 * <p>A named scene is an indirect link for a transition. Fundamentally a named 281 * scene represents a potentially arbitrary intersection point of two otherwise independent 282 * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO" 283 * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y. 284 * In this way applications may define an API for more sophisticated transitions between 285 * caller and called activities very similar to the way that <code>Intent</code> extras 286 * define APIs for arguments and data propagation between activities.</p> 287 * 288 * @param fromScene Scene to transition from 289 * @return An array of Strings naming each supported transition starting from 290 * <code>fromScene</code>. If no transitions to a named scene from the given 291 * scene are supported this function will return a String[] of length 0. 292 * @see #setTransition(ScenePort, String, TransitionPort) 293 */ 294 public String[] getTargetSceneNames(ScenePort fromScene) { 295 final ArrayMap<String, TransitionPort> m = mSceneNameTransitions.get(fromScene); 296 if (m == null) { 297 return EMPTY_STRINGS; 298 } 299 final int count = m.size(); 300 final String[] result = new String[count]; 301 for (int i = 0; i < count; i++) { 302 result[i] = m.keyAt(i); 303 } 304 return result; 305 } 306 307 /** 308 * Set a transition from a specific scene to a named scene. 309 * 310 * <p>A named scene is an indirect link for a transition. Fundamentally a named 311 * scene represents a potentially arbitrary intersection point of two otherwise independent 312 * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO" 313 * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y. 314 * In this way applications may define an API for more sophisticated transitions between 315 * caller and called activities very similar to the way that <code>Intent</code> extras 316 * define APIs for arguments and data propagation between activities.</p> 317 * 318 * @param fromScene Scene to transition from 319 * @param toName Named scene to transition to 320 * @param transition Transition to use 321 * @see #getTargetSceneNames(ScenePort) 322 */ 323 public void setTransition(ScenePort fromScene, String toName, TransitionPort transition) { 324 ArrayMap<String, TransitionPort> m = mSceneNameTransitions.get(fromScene); 325 if (m == null) { 326 m = new ArrayMap<>(); 327 mSceneNameTransitions.put(fromScene, m); 328 } 329 m.put(toName, transition); 330 } 331 332 /** 333 * Set a transition from a named scene to a concrete scene. 334 * 335 * <p>A named scene is an indirect link for a transition. Fundamentally a named 336 * scene represents a potentially arbitrary intersection point of two otherwise independent 337 * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO" 338 * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y. 339 * In this way applications may define an API for more sophisticated transitions between 340 * caller and called activities very similar to the way that <code>Intent</code> extras 341 * define APIs for arguments and data propagation between activities.</p> 342 * 343 * @param fromName Named scene to transition from 344 * @param toScene Scene to transition to 345 * @param transition Transition to use 346 * @see #getNamedTransition(String, ScenePort) 347 */ 348 public void setTransition(String fromName, ScenePort toScene, TransitionPort transition) { 349 ArrayMap<ScenePort, TransitionPort> m = mNameSceneTransitions.get(fromName); 350 if (m == null) { 351 m = new ArrayMap<>(); 352 mNameSceneTransitions.put(fromName, m); 353 } 354 m.put(toScene, transition); 355 } 356 357 public void transitionTo(ScenePort scene) { 358 // Auto transition if there is no transition declared for the Scene, but there is 359 // a root or parent view 360 changeScene(scene, getTransition(scene)); 361 } 362 363 /** 364 * This private utility class is used to listen for both OnPreDraw and 365 * OnAttachStateChange events. OnPreDraw events are the main ones we care 366 * about since that's what triggers the transition to take place. 367 * OnAttachStateChange events are also important in case the view is removed 368 * from the hierarchy before the OnPreDraw event takes place; it's used to 369 * clean up things since the OnPreDraw listener didn't get called in time. 370 */ 371 private static class MultiListener implements ViewTreeObserver.OnPreDrawListener, 372 View.OnAttachStateChangeListener { 373 374 TransitionPort mTransition; 375 376 ViewGroup mSceneRoot; 377 378 MultiListener(TransitionPort transition, ViewGroup sceneRoot) { 379 mTransition = transition; 380 mSceneRoot = sceneRoot; 381 } 382 383 private void removeListeners() { 384 mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); 385 mSceneRoot.removeOnAttachStateChangeListener(this); 386 } 387 388 @Override 389 public void onViewAttachedToWindow(View v) { 390 } 391 392 @Override 393 public void onViewDetachedFromWindow(View v) { 394 removeListeners(); 395 396 sPendingTransitions.remove(mSceneRoot); 397 ArrayList<TransitionPort> runningTransitions = getRunningTransitions().get(mSceneRoot); 398 if (runningTransitions != null && runningTransitions.size() > 0) { 399 for (TransitionPort runningTransition : runningTransitions) { 400 runningTransition.resume(mSceneRoot); 401 } 402 } 403 mTransition.clearValues(true); 404 } 405 406 @Override 407 public boolean onPreDraw() { 408 removeListeners(); 409 sPendingTransitions.remove(mSceneRoot); 410 // Add to running list, handle end to remove it 411 final ArrayMap<ViewGroup, ArrayList<TransitionPort>> runningTransitions = 412 getRunningTransitions(); 413 ArrayList<TransitionPort> currentTransitions = runningTransitions.get(mSceneRoot); 414 ArrayList<TransitionPort> previousRunningTransitions = null; 415 if (currentTransitions == null) { 416 currentTransitions = new ArrayList<>(); 417 runningTransitions.put(mSceneRoot, currentTransitions); 418 } else if (currentTransitions.size() > 0) { 419 previousRunningTransitions = new ArrayList<>(currentTransitions); 420 } 421 currentTransitions.add(mTransition); 422 mTransition.addListener(new TransitionPort.TransitionListenerAdapter() { 423 @Override 424 public void onTransitionEnd(TransitionPort transition) { 425 ArrayList<TransitionPort> currentTransitions = 426 runningTransitions.get(mSceneRoot); 427 currentTransitions.remove(transition); 428 } 429 }); 430 mTransition.captureValues(mSceneRoot, false); 431 if (previousRunningTransitions != null) { 432 for (TransitionPort runningTransition : previousRunningTransitions) { 433 runningTransition.resume(mSceneRoot); 434 } 435 } 436 mTransition.playTransition(mSceneRoot); 437 438 return true; 439 } 440 } 441} 442