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