TransitionInflater.java revision 31e8005e06acf363a0cd92b891d43f79c72dac30
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.AttributeSet; 24import android.util.Xml; 25import android.view.Gravity; 26import android.view.InflateException; 27import android.view.ViewGroup; 28import android.view.animation.AnimationUtils; 29import org.xmlpull.v1.XmlPullParser; 30import org.xmlpull.v1.XmlPullParserException; 31 32import java.io.IOException; 33import java.util.StringTokenizer; 34 35/** 36 * This class inflates scenes and transitions from resource files. 37 * 38 * Information on XML resource descriptions for transitions can be found for 39 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet}, 40 * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade}, 41 * and {@link android.R.styleable#TransitionManager}. 42 */ 43public class TransitionInflater { 44 private static final String MATCH_INSTANCE = "instance"; 45 private static final String MATCH_NAME = "name"; 46 /** To be removed before L release */ 47 private static final String MATCH_VIEW_NAME = "viewName"; 48 private static final String MATCH_ID = "id"; 49 private static final String MATCH_ITEM_ID = "itemId"; 50 51 private Context mContext; 52 53 private TransitionInflater(Context context) { 54 mContext = context; 55 } 56 57 /** 58 * Obtains the TransitionInflater from the given context. 59 */ 60 public static TransitionInflater from(Context context) { 61 return new TransitionInflater(context); 62 } 63 64 /** 65 * Loads a {@link Transition} object from a resource 66 * 67 * @param resource The resource id of the transition to load 68 * @return The loaded Transition object 69 * @throws android.content.res.Resources.NotFoundException when the 70 * transition cannot be loaded 71 */ 72 public Transition inflateTransition(int resource) { 73 XmlResourceParser parser = mContext.getResources().getXml(resource); 74 try { 75 return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null); 76 } catch (XmlPullParserException e) { 77 InflateException ex = new InflateException(e.getMessage()); 78 ex.initCause(e); 79 throw ex; 80 } catch (IOException e) { 81 InflateException ex = new InflateException( 82 parser.getPositionDescription() 83 + ": " + e.getMessage()); 84 ex.initCause(e); 85 throw ex; 86 } finally { 87 parser.close(); 88 } 89 } 90 91 /** 92 * Loads a {@link TransitionManager} object from a resource 93 * 94 * @param resource The resource id of the transition manager to load 95 * @return The loaded TransitionManager object 96 * @throws android.content.res.Resources.NotFoundException when the 97 * transition manager cannot be loaded 98 */ 99 public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) { 100 XmlResourceParser parser = mContext.getResources().getXml(resource); 101 try { 102 return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot); 103 } catch (XmlPullParserException e) { 104 InflateException ex = new InflateException(e.getMessage()); 105 ex.initCause(e); 106 throw ex; 107 } catch (IOException e) { 108 InflateException ex = new InflateException( 109 parser.getPositionDescription() 110 + ": " + e.getMessage()); 111 ex.initCause(e); 112 throw ex; 113 } finally { 114 parser.close(); 115 } 116 } 117 118 // 119 // Transition loading 120 // 121 122 private Transition createTransitionFromXml(XmlPullParser parser, 123 AttributeSet attrs, TransitionSet transitionSet) 124 throws XmlPullParserException, IOException { 125 126 Transition transition = null; 127 128 // Make sure we are on a start tag. 129 int type; 130 int depth = parser.getDepth(); 131 132 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 133 && type != XmlPullParser.END_DOCUMENT) { 134 135 boolean newTransition = false; 136 137 if (type != XmlPullParser.START_TAG) { 138 continue; 139 } 140 141 String name = parser.getName(); 142 if ("fade".equals(name)) { 143 TypedArray a = mContext.obtainStyledAttributes(attrs, 144 com.android.internal.R.styleable.Fade); 145 int fadingMode = a.getInt(com.android.internal.R.styleable.Fade_fadingMode, 146 Fade.IN | Fade.OUT); 147 transition = new Fade(fadingMode); 148 newTransition = true; 149 } else if ("changeBounds".equals(name)) { 150 transition = new ChangeBounds(); 151 newTransition = true; 152 } else if ("slide".equals(name)) { 153 transition = createSlideTransition(attrs); 154 newTransition = true; 155 } else if ("explode".equals(name)) { 156 transition = new Explode(); 157 newTransition = true; 158 } else if ("moveImage".equals(name)) { 159 transition = new MoveImage(); 160 newTransition = true; 161 } else if ("changeImageTransform".equals(name)) { 162 transition = new ChangeImageTransform(); 163 newTransition = true; 164 } else if ("changeTransform".equals(name)) { 165 transition = new ChangeTransform(); 166 newTransition = true; 167 } else if ("changeClipBounds".equals(name)) { 168 transition = new ChangeClipBounds(); 169 newTransition = true; 170 } else if ("autoTransition".equals(name)) { 171 transition = new AutoTransition(); 172 newTransition = true; 173 } else if ("recolor".equals(name)) { 174 transition = new Recolor(); 175 newTransition = true; 176 } else if ("transitionSet".equals(name)) { 177 transition = new TransitionSet(); 178 TypedArray a = mContext.obtainStyledAttributes(attrs, 179 com.android.internal.R.styleable.TransitionSet); 180 int ordering = a.getInt( 181 com.android.internal.R.styleable.TransitionSet_transitionOrdering, 182 TransitionSet.ORDERING_TOGETHER); 183 ((TransitionSet) transition).setOrdering(ordering); 184 createTransitionFromXml(parser, attrs, ((TransitionSet) transition)); 185 a.recycle(); 186 newTransition = true; 187 } else if ("transition".equals(name)) { 188 transition = createCustomTransition(attrs); 189 } else if ("targets".equals(name)) { 190 if (parser.getDepth() - 1 > depth && transition != null) { 191 // We're inside the child tag - add targets to the child 192 getTargetIds(parser, attrs, transition); 193 } else if (parser.getDepth() - 1 == depth && transitionSet != null) { 194 // add targets to the set 195 getTargetIds(parser, attrs, transitionSet); 196 } 197 } 198 if (transition != null || "targets".equals(name)) { 199 if (newTransition) { 200 loadTransition(transition, attrs); 201 if (transitionSet != null) { 202 transitionSet.addTransition(transition); 203 } 204 } 205 } else { 206 throw new RuntimeException("Unknown scene name: " + parser.getName()); 207 } 208 } 209 210 return transition; 211 } 212 213 private Transition createCustomTransition(AttributeSet attrs) { 214 String className = attrs.getAttributeValue(null, "class"); 215 216 if (className == null) { 217 throw new RuntimeException("transition tag must have a 'class' attribute"); 218 } 219 220 try { 221 Class c = Class.forName(className); 222 if (!Transition.class.isAssignableFrom(c)) { 223 throw new RuntimeException("transition class must be a Transition: " + className); 224 } 225 return (Transition) c.newInstance(); 226 } catch (InstantiationException e) { 227 throw new RuntimeException("Could not instantiate transition class", e); 228 } catch (IllegalAccessException e) { 229 throw new RuntimeException("Could not access default constructor for transition class " 230 + className, e); 231 } catch (ClassNotFoundException e) { 232 throw new RuntimeException("Could not find transition class " + className, e); 233 } 234 } 235 236 private Slide createSlideTransition(AttributeSet attrs) { 237 TypedArray a = mContext.obtainStyledAttributes(attrs, 238 com.android.internal.R.styleable.Slide); 239 int edge = a.getInt(com.android.internal.R.styleable.Slide_slideEdge, Gravity.BOTTOM); 240 Slide slide = new Slide(edge); 241 a.recycle(); 242 return slide; 243 } 244 245 private void getTargetIds(XmlPullParser parser, 246 AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException { 247 248 // Make sure we are on a start tag. 249 int type; 250 int depth = parser.getDepth(); 251 252 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 253 && type != XmlPullParser.END_DOCUMENT) { 254 255 if (type != XmlPullParser.START_TAG) { 256 continue; 257 } 258 259 String name = parser.getName(); 260 if (name.equals("target")) { 261 TypedArray a = mContext.obtainStyledAttributes(attrs, 262 com.android.internal.R.styleable.TransitionTarget); 263 int id = a.getResourceId( 264 com.android.internal.R.styleable.TransitionTarget_targetId, -1); 265 String transitionName; 266 if (id >= 0) { 267 transition.addTarget(id); 268 } else if ((id = a.getResourceId( 269 com.android.internal.R.styleable.TransitionTarget_excludeId, -1)) >= 0) { 270 transition.excludeTarget(id, true); 271 } else if ((transitionName = a.getString( 272 com.android.internal.R.styleable.TransitionTarget_targetName)) 273 != null) { 274 transition.addTarget(transitionName); 275 } else if ((transitionName = a.getString( 276 com.android.internal.R.styleable.TransitionTarget_excludeName)) 277 != null) { 278 transition.excludeTarget(transitionName, true); 279 } else { 280 String className = a.getString( 281 com.android.internal.R.styleable.TransitionTarget_excludeClass); 282 try { 283 if (className != null) { 284 Class clazz = Class.forName(className); 285 transition.excludeTarget(clazz, true); 286 } else if ((className = a.getString( 287 com.android.internal.R.styleable.TransitionTarget_targetClass)) 288 != null) { 289 Class clazz = Class.forName(className); 290 transition.addTarget(clazz); 291 } 292 } catch (ClassNotFoundException e) { 293 throw new RuntimeException("Could not create " + className, e); 294 } 295 } 296 } else { 297 throw new RuntimeException("Unknown scene name: " + parser.getName()); 298 } 299 } 300 } 301 302 private int[] parseMatchOrder(String matchOrderString) { 303 StringTokenizer st = new StringTokenizer(matchOrderString, ","); 304 int matches[] = new int[st.countTokens()]; 305 int index = 0; 306 while (st.hasMoreTokens()) { 307 String token = st.nextToken().trim(); 308 if (MATCH_ID.equalsIgnoreCase(token)) { 309 matches[index] = Transition.MATCH_ID; 310 } else if (MATCH_INSTANCE.equalsIgnoreCase(token)) { 311 matches[index] = Transition.MATCH_INSTANCE; 312 } else if (MATCH_NAME.equalsIgnoreCase(token)) { 313 matches[index] = Transition.MATCH_NAME; 314 } else if (MATCH_VIEW_NAME.equalsIgnoreCase(token)) { 315 matches[index] = Transition.MATCH_NAME; 316 } else if (MATCH_ITEM_ID.equalsIgnoreCase(token)) { 317 matches[index] = Transition.MATCH_ITEM_ID; 318 } else if (token.isEmpty()) { 319 int[] smallerMatches = new int[matches.length - 1]; 320 System.arraycopy(matches, 0, smallerMatches, 0, index); 321 matches = smallerMatches; 322 index--; 323 } else { 324 throw new RuntimeException("Unknown match type in matchOrder: '" + token + "'"); 325 } 326 index++; 327 } 328 return matches; 329 } 330 331 private Transition loadTransition(Transition transition, AttributeSet attrs) 332 throws Resources.NotFoundException { 333 334 TypedArray a = 335 mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition); 336 long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1); 337 if (duration >= 0) { 338 transition.setDuration(duration); 339 } 340 long startDelay = a.getInt(com.android.internal.R.styleable.Transition_startDelay, -1); 341 if (startDelay > 0) { 342 transition.setStartDelay(startDelay); 343 } 344 final int resID = 345 a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0); 346 if (resID > 0) { 347 transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID)); 348 } 349 String matchOrder = 350 a.getString(com.android.internal.R.styleable.Transition_matchOrder); 351 if (matchOrder != null) { 352 transition.setMatchOrder(parseMatchOrder(matchOrder)); 353 } 354 a.recycle(); 355 if (transition instanceof Visibility) { 356 a = mContext.obtainStyledAttributes(attrs, 357 com.android.internal.R.styleable.VisibilityTransition); 358 int mode = a.getInt( 359 com.android.internal.R.styleable.VisibilityTransition_visibilityMode, 0); 360 a.recycle(); 361 if (mode != 0) { 362 ((Visibility)transition).setMode(mode); 363 } 364 } 365 return transition; 366 } 367 368 // 369 // TransitionManager loading 370 // 371 372 private TransitionManager createTransitionManagerFromXml(XmlPullParser parser, 373 AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException { 374 375 // Make sure we are on a start tag. 376 int type; 377 int depth = parser.getDepth(); 378 TransitionManager transitionManager = null; 379 380 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 381 && type != XmlPullParser.END_DOCUMENT) { 382 383 if (type != XmlPullParser.START_TAG) { 384 continue; 385 } 386 387 String name = parser.getName(); 388 if (name.equals("transitionManager")) { 389 transitionManager = new TransitionManager(); 390 } else if (name.equals("transition") && (transitionManager != null)) { 391 loadTransition(attrs, sceneRoot, transitionManager); 392 } else { 393 throw new RuntimeException("Unknown scene name: " + parser.getName()); 394 } 395 } 396 return transitionManager; 397 } 398 399 private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot, 400 TransitionManager transitionManager) throws Resources.NotFoundException { 401 402 TypedArray a = mContext.obtainStyledAttributes(attrs, 403 com.android.internal.R.styleable.TransitionManager); 404 int transitionId = a.getResourceId( 405 com.android.internal.R.styleable.TransitionManager_transition, -1); 406 int fromId = a.getResourceId( 407 com.android.internal.R.styleable.TransitionManager_fromScene, -1); 408 Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext); 409 int toId = a.getResourceId( 410 com.android.internal.R.styleable.TransitionManager_toScene, -1); 411 Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext); 412 413 if (transitionId >= 0) { 414 Transition transition = inflateTransition(transitionId); 415 if (transition != null) { 416 if (toScene == null) { 417 throw new RuntimeException("No toScene for transition ID " + transitionId); 418 } 419 if (fromScene == null) { 420 transitionManager.setTransition(toScene, transition); 421 } else { 422 transitionManager.setTransition(fromScene, toScene, transition); 423 } 424 } 425 } 426 a.recycle(); 427 } 428} 429