TransitionInflater.java revision 125578a8637a9ad5e7430d16b9fc0096a8b596d7
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 ("changeTransform".equals(name)) { 162 transition = new ChangeTransform(); 163 newTransition = true; 164 } else if ("changeClipBounds".equals(name)) { 165 transition = new ChangeClipBounds(); 166 newTransition = true; 167 } else if ("autoTransition".equals(name)) { 168 transition = new AutoTransition(); 169 newTransition = true; 170 } else if ("recolor".equals(name)) { 171 transition = new Recolor(); 172 newTransition = true; 173 } else if ("transitionSet".equals(name)) { 174 transition = new TransitionSet(); 175 TypedArray a = mContext.obtainStyledAttributes(attrs, 176 com.android.internal.R.styleable.TransitionSet); 177 int ordering = a.getInt( 178 com.android.internal.R.styleable.TransitionSet_transitionOrdering, 179 TransitionSet.ORDERING_TOGETHER); 180 ((TransitionSet) transition).setOrdering(ordering); 181 createTransitionFromXml(parser, attrs, ((TransitionSet) transition)); 182 a.recycle(); 183 newTransition = true; 184 } else if ("targets".equals(name)) { 185 if (parser.getDepth() - 1 > depth && transition != null) { 186 // We're inside the child tag - add targets to the child 187 getTargetIds(parser, attrs, transition); 188 } else if (parser.getDepth() - 1 == depth && transitionSet != null) { 189 // add targets to the set 190 getTargetIds(parser, attrs, transitionSet); 191 } 192 } 193 if (transition != null || "targets".equals(name)) { 194 if (newTransition) { 195 loadTransition(transition, attrs); 196 if (transitionSet != null) { 197 transitionSet.addTransition(transition); 198 } 199 } 200 } else { 201 throw new RuntimeException("Unknown scene name: " + parser.getName()); 202 } 203 } 204 205 return transition; 206 } 207 208 private Slide createSlideTransition(AttributeSet attrs) { 209 TypedArray a = mContext.obtainStyledAttributes(attrs, 210 com.android.internal.R.styleable.Slide); 211 int edge = a.getInt(com.android.internal.R.styleable.Slide_slideEdge, Gravity.BOTTOM); 212 Slide slide = new Slide(edge); 213 a.recycle(); 214 return slide; 215 } 216 217 private void getTargetIds(XmlPullParser parser, 218 AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException { 219 220 // Make sure we are on a start tag. 221 int type; 222 int depth = parser.getDepth(); 223 224 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 225 && type != XmlPullParser.END_DOCUMENT) { 226 227 if (type != XmlPullParser.START_TAG) { 228 continue; 229 } 230 231 String name = parser.getName(); 232 if (name.equals("target")) { 233 TypedArray a = mContext.obtainStyledAttributes(attrs, 234 com.android.internal.R.styleable.TransitionTarget); 235 int id = a.getResourceId( 236 com.android.internal.R.styleable.TransitionTarget_targetId, -1); 237 String transitionName; 238 if (id >= 0) { 239 transition.addTarget(id); 240 } else if ((id = a.getResourceId( 241 com.android.internal.R.styleable.TransitionTarget_excludeId, -1)) >= 0) { 242 transition.excludeTarget(id, true); 243 } else if ((transitionName = a.getString( 244 com.android.internal.R.styleable.TransitionTarget_targetName)) 245 != null) { 246 transition.addTarget(transitionName); 247 } else if ((transitionName = a.getString( 248 com.android.internal.R.styleable.TransitionTarget_excludeName)) 249 != null) { 250 transition.excludeTarget(transitionName, true); 251 } else { 252 String className = a.getString( 253 com.android.internal.R.styleable.TransitionTarget_excludeClass); 254 try { 255 if (className != null) { 256 Class clazz = Class.forName(className); 257 transition.excludeTarget(clazz, true); 258 } else if ((className = a.getString( 259 com.android.internal.R.styleable.TransitionTarget_targetClass)) 260 != null) { 261 Class clazz = Class.forName(className); 262 transition.addTarget(clazz); 263 } 264 } catch (ClassNotFoundException e) { 265 throw new RuntimeException("Could not create " + className, e); 266 } 267 } 268 } else { 269 throw new RuntimeException("Unknown scene name: " + parser.getName()); 270 } 271 } 272 } 273 274 private int[] parseMatchOrder(String matchOrderString) { 275 StringTokenizer st = new StringTokenizer(matchOrderString, ","); 276 int matches[] = new int[st.countTokens()]; 277 int index = 0; 278 while (st.hasMoreTokens()) { 279 String token = st.nextToken().trim(); 280 if (MATCH_ID.equalsIgnoreCase(token)) { 281 matches[index] = Transition.MATCH_ID; 282 } else if (MATCH_INSTANCE.equalsIgnoreCase(token)) { 283 matches[index] = Transition.MATCH_INSTANCE; 284 } else if (MATCH_NAME.equalsIgnoreCase(token)) { 285 matches[index] = Transition.MATCH_NAME; 286 } else if (MATCH_VIEW_NAME.equalsIgnoreCase(token)) { 287 matches[index] = Transition.MATCH_NAME; 288 } else if (MATCH_ITEM_ID.equalsIgnoreCase(token)) { 289 matches[index] = Transition.MATCH_ITEM_ID; 290 } else if (token.isEmpty()) { 291 int[] smallerMatches = new int[matches.length - 1]; 292 System.arraycopy(matches, 0, smallerMatches, 0, index); 293 matches = smallerMatches; 294 index--; 295 } else { 296 throw new RuntimeException("Unknown match type in matchOrder: '" + token + "'"); 297 } 298 index++; 299 } 300 return matches; 301 } 302 303 private Transition loadTransition(Transition transition, AttributeSet attrs) 304 throws Resources.NotFoundException { 305 306 TypedArray a = 307 mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition); 308 long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1); 309 if (duration >= 0) { 310 transition.setDuration(duration); 311 } 312 long startDelay = a.getInt(com.android.internal.R.styleable.Transition_startDelay, -1); 313 if (startDelay > 0) { 314 transition.setStartDelay(startDelay); 315 } 316 final int resID = 317 a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0); 318 if (resID > 0) { 319 transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID)); 320 } 321 String matchOrder = 322 a.getString(com.android.internal.R.styleable.Transition_matchOrder); 323 if (matchOrder != null) { 324 transition.setMatchOrder(parseMatchOrder(matchOrder)); 325 } 326 a.recycle(); 327 return transition; 328 } 329 330 // 331 // TransitionManager loading 332 // 333 334 private TransitionManager createTransitionManagerFromXml(XmlPullParser parser, 335 AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException { 336 337 // Make sure we are on a start tag. 338 int type; 339 int depth = parser.getDepth(); 340 TransitionManager transitionManager = null; 341 342 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 343 && type != XmlPullParser.END_DOCUMENT) { 344 345 if (type != XmlPullParser.START_TAG) { 346 continue; 347 } 348 349 String name = parser.getName(); 350 if (name.equals("transitionManager")) { 351 transitionManager = new TransitionManager(); 352 } else if (name.equals("transition") && (transitionManager != null)) { 353 loadTransition(attrs, sceneRoot, transitionManager); 354 } else { 355 throw new RuntimeException("Unknown scene name: " + parser.getName()); 356 } 357 } 358 return transitionManager; 359 } 360 361 private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot, 362 TransitionManager transitionManager) throws Resources.NotFoundException { 363 364 TypedArray a = mContext.obtainStyledAttributes(attrs, 365 com.android.internal.R.styleable.TransitionManager); 366 int transitionId = a.getResourceId( 367 com.android.internal.R.styleable.TransitionManager_transition, -1); 368 int fromId = a.getResourceId( 369 com.android.internal.R.styleable.TransitionManager_fromScene, -1); 370 Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext); 371 int toId = a.getResourceId( 372 com.android.internal.R.styleable.TransitionManager_toScene, -1); 373 Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext); 374 375 if (transitionId >= 0) { 376 Transition transition = inflateTransition(transitionId); 377 if (transition != null) { 378 if (toScene == null) { 379 throw new RuntimeException("No toScene for transition ID " + transitionId); 380 } 381 if (fromScene == null) { 382 transitionManager.setTransition(toScene, transition); 383 } else { 384 transitionManager.setTransition(fromScene, toScene, transition); 385 } 386 } 387 } 388 a.recycle(); 389 } 390} 391