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