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.SparseArray; 21 import android.view.LayoutInflater; 22 import android.view.View; 23 import android.view.ViewGroup; 24 25 /** 26 * A scene represents the collection of values that various properties in the 27 * View hierarchy will have when the scene is applied. A Scene can be 28 * configured to automatically run a Transition when it is applied, which will 29 * animate the various property changes that take place during the 30 * scene change. 31 */ 32 public final class Scene { 33 34 private Context mContext; 35 private int mLayoutId = -1; 36 private ViewGroup mSceneRoot; 37 private ViewGroup mLayout; // alternative to layoutId 38 Runnable mEnterAction, mExitAction; 39 40 /** 41 * Returns a Scene described by the resource file associated with the given 42 * <code>layoutId</code> parameter. If such a Scene has already been created for 43 * the given <code>sceneRoot</code>, that same Scene will be returned. 44 * This caching of layoutId-based scenes enables sharing of common scenes 45 * between those created in code and those referenced by {@link TransitionManager} 46 * XML resource files. 47 * 48 * @param sceneRoot The root of the hierarchy in which scene changes 49 * and transitions will take place. 50 * @param layoutId The id of a standard layout resource file. 51 * @param context The context used in the process of inflating 52 * the layout resource. 53 * @return The scene for the given root and layout id 54 */ 55 public static Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) { 56 SparseArray<Scene> scenes = (SparseArray<Scene>) sceneRoot.getTag( 57 com.android.internal.R.id.scene_layoutid_cache); 58 if (scenes == null) { 59 scenes = new SparseArray<Scene>(); 60 sceneRoot.setTagInternal(com.android.internal.R.id.scene_layoutid_cache, scenes); 61 } 62 Scene scene = scenes.get(layoutId); 63 if (scene != null) { 64 return scene; 65 } else { 66 scene = new Scene(sceneRoot, layoutId, context); 67 scenes.put(layoutId, scene); 68 return scene; 69 } 70 } 71 72 /** 73 * Constructs a Scene with no information about how values will change 74 * when this scene is applied. This constructor might be used when 75 * a Scene is created with the intention of being dynamically configured, 76 * through setting {@link #setEnterAction(Runnable)} and possibly 77 * {@link #setExitAction(Runnable)}. 78 * 79 * @param sceneRoot The root of the hierarchy in which scene changes 80 * and transitions will take place. 81 */ 82 public Scene(ViewGroup sceneRoot) { 83 mSceneRoot = sceneRoot; 84 } 85 86 /** 87 * Constructs a Scene which, when entered, will remove any 88 * children from the sceneRoot container and will inflate and add 89 * the hierarchy specified by the layoutId resource file. 90 * 91 * <p>This method is hidden because layoutId-based scenes should be 92 * created by the caching factory method {@link Scene#getCurrentScene(View)}.</p> 93 * 94 * @param sceneRoot The root of the hierarchy in which scene changes 95 * and transitions will take place. 96 * @param layoutId The id of a resource file that defines the view 97 * hierarchy of this scene. 98 * @param context The context used in the process of inflating 99 * the layout resource. 100 */ 101 private Scene(ViewGroup sceneRoot, int layoutId, Context context) { 102 mContext = context; 103 mSceneRoot = sceneRoot; 104 mLayoutId = layoutId; 105 } 106 107 /** 108 * Constructs a Scene which, when entered, will remove any 109 * children from the sceneRoot container and add the layout 110 * object as a new child of that container. 111 * 112 * @param sceneRoot The root of the hierarchy in which scene changes 113 * and transitions will take place. 114 * @param layout The view hierarchy of this scene, added as a child 115 * of sceneRoot when this scene is entered. 116 */ 117 public Scene(ViewGroup sceneRoot, ViewGroup layout) { 118 mSceneRoot = sceneRoot; 119 mLayout = layout; 120 } 121 122 /** 123 * Gets the root of the scene, which is the root of the view hierarchy 124 * affected by changes due to this scene, and which will be animated 125 * when this scene is entered. 126 * 127 * @return The root of the view hierarchy affected by this scene. 128 */ 129 public ViewGroup getSceneRoot() { 130 return mSceneRoot; 131 } 132 133 /** 134 * Exits this scene, if it is the current scene 135 * on the scene's {@link #getSceneRoot() scene root}. The current scene is 136 * set when {@link #enter() entering} a scene. 137 * Exiting a scene runs the {@link #setExitAction(Runnable) exit action} 138 * if there is one. 139 */ 140 public void exit() { 141 if (getCurrentScene(mSceneRoot) == this) { 142 if (mExitAction != null) { 143 mExitAction.run(); 144 } 145 } 146 } 147 148 /** 149 * Enters this scene, which entails changing all values that 150 * are specified by this scene. These may be values associated 151 * with a layout view group or layout resource file which will 152 * now be added to the scene root, or it may be values changed by 153 * an {@link #setEnterAction(Runnable)} enter action}, or a 154 * combination of the these. No transition will be run when the 155 * scene is entered. To get transition behavior in scene changes, 156 * use one of the methods in {@link TransitionManager} instead. 157 */ 158 public void enter() { 159 160 // Apply layout change, if any 161 if (mLayoutId > 0 || mLayout != null) { 162 // empty out parent container before adding to it 163 getSceneRoot().removeAllViews(); 164 165 if (mLayoutId > 0) { 166 LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot); 167 } else { 168 mSceneRoot.addView(mLayout); 169 } 170 } 171 172 // Notify next scene that it is entering. Subclasses may override to configure scene. 173 if (mEnterAction != null) { 174 mEnterAction.run(); 175 } 176 177 setCurrentScene(mSceneRoot, this); 178 } 179 180 /** 181 * Set the scene that the given view is in. The current scene is set only 182 * on the root view of a scene, not for every view in that hierarchy. This 183 * information is used by Scene to determine whether there is a previous 184 * scene which should be exited before the new scene is entered. 185 * 186 * @param view The view on which the current scene is being set 187 */ 188 static void setCurrentScene(View view, Scene scene) { 189 view.setTagInternal(com.android.internal.R.id.current_scene, scene); 190 } 191 192 /** 193 * Gets the current {@link Scene} set on the given view. A scene is set on a view 194 * only if that view is the scene root. 195 * 196 * @return The current Scene set on this view. A value of null indicates that 197 * no Scene is currently set. 198 */ 199 static Scene getCurrentScene(View view) { 200 return (Scene) view.getTag(com.android.internal.R.id.current_scene); 201 } 202 203 /** 204 * Scenes that are not defined with layout resources or 205 * hierarchies, or which need to perform additional steps 206 * after those hierarchies are changed to, should set an enter 207 * action, and possibly an exit action as well. An enter action 208 * will cause Scene to call back into application code to do 209 * anything else the application needs after transitions have 210 * captured pre-change values and after any other scene changes 211 * have been applied, such as the layout (if any) being added to 212 * the view hierarchy. After this method is called, Transitions will 213 * be played. 214 * 215 * @param action The runnable whose {@link Runnable#run() run()} method will 216 * be called when this scene is entered 217 * @see #setExitAction(Runnable) 218 * @see Scene#Scene(ViewGroup, int, Context) 219 * @see Scene#Scene(ViewGroup, ViewGroup) 220 */ 221 public void setEnterAction(Runnable action) { 222 mEnterAction = action; 223 } 224 225 /** 226 * Scenes that are not defined with layout resources or 227 * hierarchies, or which need to perform additional steps 228 * after those hierarchies are changed to, should set an enter 229 * action, and possibly an exit action as well. An exit action 230 * will cause Scene to call back into application code to do 231 * anything the application needs to do after applicable transitions have 232 * captured pre-change values, but before any other scene changes 233 * have been applied, such as the new layout (if any) being added to 234 * the view hierarchy. After this method is called, the next scene 235 * will be entered, including a call to {@link #setEnterAction(Runnable)} 236 * if an enter action is set. 237 * 238 * @see #setEnterAction(Runnable) 239 * @see Scene#Scene(ViewGroup, int, Context) 240 * @see Scene#Scene(ViewGroup, ViewGroup) 241 */ 242 public void setExitAction(Runnable action) { 243 mExitAction = action; 244 } 245 246 247 /** 248 * Returns whether this Scene was created by a layout resource file, determined 249 * by the layoutId passed into 250 * {@link #getSceneForLayout(android.view.ViewGroup, int, android.content.Context)}. 251 * This is called by TransitionManager to determine whether it is safe for views from 252 * this scene to be removed from their parents when the scene is exited, which is 253 * used by {@link Fade} to fade these views out (the views must be removed from 254 * their parent in order to add them to the overlay for fading purposes). If a 255 * Scene is not based on a resource file, then the impact of removing views 256 * arbitrarily is unknown and should be avoided. 257 */ 258 boolean isCreatedFromLayoutResource() { 259 return (mLayoutId > 0); 260 } 261 }