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