TransitionInflater.java revision d82c8ac4db7091d2e976af4c89a1734465d20cd2
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.content.res.Resources; 21import android.content.res.TypedArray; 22import android.content.res.XmlResourceParser; 23import android.util.ArrayMap; 24import android.util.AttributeSet; 25import android.util.SparseArray; 26import android.util.Xml; 27import android.view.InflateException; 28import android.view.ViewGroup; 29import android.view.animation.AnimationUtils; 30import org.xmlpull.v1.XmlPullParser; 31import org.xmlpull.v1.XmlPullParserException; 32 33import java.io.IOException; 34import java.util.ArrayList; 35 36/** 37 * This class inflates scenes and transitions from resource files. 38 * 39 * Information on XML resource descriptions for transitions can be found for 40 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet}, 41 * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade}, 42 * and {@link android.R.styleable#TransitionManager}. 43 */ 44public class TransitionInflater { 45 46 // We only need one inflater for any given context. Also, this allows us to associate 47 // ids with unique instances per-Context, used to avoid re-inflating 48 // already-inflated resources into new/different instances 49 private static final ArrayMap<Context, TransitionInflater> sInflaterMap = 50 new ArrayMap<Context, TransitionInflater>(); 51 52 private Context mContext; 53 // TODO: do we need id maps for transitions and transitionMgrs as well? 54 SparseArray<Scene> mScenes = new SparseArray<Scene>(); 55 56 private TransitionInflater(Context context) { 57 mContext = context; 58 } 59 60 /** 61 * Obtains the TransitionInflater from the given context. 62 */ 63 public static TransitionInflater from(Context context) { 64 TransitionInflater inflater = sInflaterMap.get(context); 65 if (inflater != null) { 66 return inflater; 67 } 68 inflater = new TransitionInflater(context); 69 sInflaterMap.put(context, inflater); 70 return inflater; 71 } 72 73 /** 74 * Loads a {@link Transition} object from a resource 75 * 76 * @param resource The resource id of the transition to load 77 * @return The loaded Transition object 78 * @throws android.content.res.Resources.NotFoundException when the 79 * transition cannot be loaded 80 */ 81 public Transition inflateTransition(int resource) { 82 XmlResourceParser parser = mContext.getResources().getXml(resource); 83 try { 84 return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null); 85 } catch (XmlPullParserException e) { 86 InflateException ex = new InflateException(e.getMessage()); 87 ex.initCause(e); 88 throw ex; 89 } catch (IOException e) { 90 InflateException ex = new InflateException( 91 parser.getPositionDescription() 92 + ": " + e.getMessage()); 93 ex.initCause(e); 94 throw ex; 95 } finally { 96 parser.close(); 97 } 98 } 99 100 /** 101 * Loads a {@link TransitionManager} object from a resource 102 * 103 * 104 * 105 * @param resource The resource id of the transition manager to load 106 * @return The loaded TransitionManager object 107 * @throws android.content.res.Resources.NotFoundException when the 108 * transition manager cannot be loaded 109 */ 110 public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) { 111 XmlResourceParser parser = mContext.getResources().getXml(resource); 112 try { 113 return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot); 114 } catch (XmlPullParserException e) { 115 InflateException ex = new InflateException(e.getMessage()); 116 ex.initCause(e); 117 throw ex; 118 } catch (IOException e) { 119 InflateException ex = new InflateException( 120 parser.getPositionDescription() 121 + ": " + e.getMessage()); 122 ex.initCause(e); 123 throw ex; 124 } finally { 125 parser.close(); 126 } 127 } 128 129 // 130 // Transition loading 131 // 132 133 private Transition createTransitionFromXml(XmlPullParser parser, 134 AttributeSet attrs, TransitionSet transitionSet) 135 throws XmlPullParserException, IOException { 136 137 Transition transition = null; 138 139 // Make sure we are on a start tag. 140 int type; 141 int depth = parser.getDepth(); 142 143 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 144 && type != XmlPullParser.END_DOCUMENT) { 145 146 boolean newTransition = false; 147 148 if (type != XmlPullParser.START_TAG) { 149 continue; 150 } 151 152 String name = parser.getName(); 153 if ("fade".equals(name)) { 154 TypedArray a = mContext.obtainStyledAttributes(attrs, 155 com.android.internal.R.styleable.Fade); 156 int fadingMode = a.getInt(com.android.internal.R.styleable.Fade_fadingMode, 157 Fade.IN | Fade.OUT); 158 transition = new Fade(fadingMode); 159 newTransition = true; 160 } else if ("changeBounds".equals(name)) { 161 transition = new ChangeBounds(); 162 newTransition = true; 163 } else if ("slide".equals(name)) { 164 transition = new Slide(); 165 newTransition = true; 166 } else if ("autoTransition".equals(name)) { 167 transition = new AutoTransition(); 168 newTransition = true; 169 } else if ("recolor".equals(name)) { 170 transition = new Recolor(); 171 newTransition = true; 172 } else if ("set".equals(name)) { 173 transition = new TransitionSet(); 174 TypedArray a = mContext.obtainStyledAttributes(attrs, 175 com.android.internal.R.styleable.TransitionSet); 176 int ordering = a.getInt( 177 com.android.internal.R.styleable.TransitionSet_transitionOrdering, 178 TransitionSet.ORDERING_TOGETHER); 179 ((TransitionSet) transition).setOrdering(ordering); 180 createTransitionFromXml(parser, attrs, ((TransitionSet) transition)); 181 a.recycle(); 182 newTransition = true; 183 } else if ("targets".equals(name)) { 184 if (parser.getDepth() - 1 > depth && transition != null) { 185 // We're inside the child tag - add targets to the child 186 getTargetIds(parser, attrs, transition); 187 } else if (parser.getDepth() - 1 == depth && transitionSet != null) { 188 // add targets to the set 189 getTargetIds(parser, attrs, transitionSet); 190 } 191 } 192 if (transition != null || "targets".equals(name)) { 193 if (newTransition) { 194 loadTransition(transition, attrs); 195 if (transitionSet != null) { 196 transitionSet.addTransition(transition); 197 } 198 } 199 } else { 200 throw new RuntimeException("Unknown scene name: " + parser.getName()); 201 } 202 } 203 204 return transition; 205 } 206 207 private void getTargetIds(XmlPullParser parser, 208 AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException { 209 210 // Make sure we are on a start tag. 211 int type; 212 int depth = parser.getDepth(); 213 214 ArrayList<Integer> targetIds = new ArrayList<Integer>(); 215 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 216 && type != XmlPullParser.END_DOCUMENT) { 217 218 if (type != XmlPullParser.START_TAG) { 219 continue; 220 } 221 222 String name = parser.getName(); 223 if (name.equals("target")) { 224 TypedArray a = mContext.obtainStyledAttributes(attrs, 225 com.android.internal.R.styleable.TransitionTarget); 226 int id = a.getResourceId( 227 com.android.internal.R.styleable.TransitionTarget_targetId, -1); 228 if (id >= 0) { 229 targetIds.add(id); 230 } 231 } else { 232 throw new RuntimeException("Unknown scene name: " + parser.getName()); 233 } 234 } 235 int numTargets = targetIds.size(); 236 if (numTargets > 0) { 237 for (int i = 0; i < numTargets; ++i) { 238 transition.addTargetId(targetIds.get(i)); 239 } 240 } 241 } 242 243 private Transition loadTransition(Transition transition, AttributeSet attrs) 244 throws Resources.NotFoundException { 245 246 TypedArray a = 247 mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition); 248 long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1); 249 if (duration >= 0) { 250 transition.setDuration(duration); 251 } 252 long startDelay = a.getInt(com.android.internal.R.styleable.Transition_startDelay, -1); 253 if (startDelay > 0) { 254 transition.setStartDelay(startDelay); 255 } 256 final int resID = 257 a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0); 258 if (resID > 0) { 259 transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID)); 260 } 261 a.recycle(); 262 return transition; 263 } 264 265 // 266 // TransitionManager loading 267 // 268 269 private TransitionManager createTransitionManagerFromXml(XmlPullParser parser, 270 AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException { 271 272 // Make sure we are on a start tag. 273 int type; 274 int depth = parser.getDepth(); 275 TransitionManager transitionManager = null; 276 277 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 278 && type != XmlPullParser.END_DOCUMENT) { 279 280 if (type != XmlPullParser.START_TAG) { 281 continue; 282 } 283 284 String name = parser.getName(); 285 if (name.equals("transitionManager")) { 286 transitionManager = new TransitionManager(); 287 } else if (name.equals("transition") && (transitionManager != null)) { 288 loadTransition(attrs, sceneRoot, transitionManager); 289 } else { 290 throw new RuntimeException("Unknown scene name: " + parser.getName()); 291 } 292 } 293 return transitionManager; 294 } 295 296 private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot, 297 TransitionManager transitionManager) throws Resources.NotFoundException { 298 299 TypedArray a = mContext.obtainStyledAttributes(attrs, 300 com.android.internal.R.styleable.TransitionManager); 301 int transitionId = a.getResourceId( 302 com.android.internal.R.styleable.TransitionManager_transition, -1); 303 Scene fromScene = null, toScene = null; 304 int fromId = a.getResourceId( 305 com.android.internal.R.styleable.TransitionManager_fromScene, -1); 306 if (fromId >= 0) fromScene = Scene.getSceneForLayout(sceneRoot, fromId, mContext); 307 int toId = a.getResourceId( 308 com.android.internal.R.styleable.TransitionManager_toScene, -1); 309 if (toId >= 0) toScene = Scene.getSceneForLayout(sceneRoot, toId, mContext); 310 if (transitionId >= 0) { 311 Transition transition = inflateTransition(transitionId); 312 if (transition != null) { 313 if (fromScene != null) { 314 if (toScene == null){ 315 throw new RuntimeException("No matching toScene for given fromScene " + 316 "for transition ID " + transitionId); 317 } else { 318 transitionManager.setTransition(fromScene, toScene, transition); 319 } 320 } else if (toId >= 0) { 321 transitionManager.setTransition(toScene, transition); 322 } 323 } 324 } 325 a.recycle(); 326 } 327} 328