TransitionInflater.java revision f3de5a2e34412710234cd1f5f3933ce266ea0e49
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 com.android.internal.R; 20 21import org.xmlpull.v1.XmlPullParser; 22import org.xmlpull.v1.XmlPullParserException; 23 24import android.content.Context; 25import android.content.res.Resources; 26import android.content.res.TypedArray; 27import android.content.res.XmlResourceParser; 28import android.util.ArrayMap; 29import android.util.AttributeSet; 30import android.util.Xml; 31import android.view.InflateException; 32import android.view.ViewGroup; 33 34import java.io.IOException; 35import java.lang.reflect.Constructor; 36import java.lang.reflect.InvocationTargetException; 37 38/** 39 * This class inflates scenes and transitions from resource files. 40 * 41 * Information on XML resource descriptions for transitions can be found for 42 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet}, 43 * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade}, 44 * and {@link android.R.styleable#TransitionManager}. 45 */ 46public class TransitionInflater { 47 48 private static final Class<?>[] sConstructorSignature = new Class[] { 49 Context.class, AttributeSet.class}; 50 private final static ArrayMap<String, Constructor> sConstructors = 51 new ArrayMap<String, Constructor>(); 52 53 private Context mContext; 54 55 private TransitionInflater(Context context) { 56 mContext = context; 57 } 58 59 /** 60 * Obtains the TransitionInflater from the given context. 61 */ 62 public static TransitionInflater from(Context context) { 63 return new TransitionInflater(context); 64 } 65 66 /** 67 * Loads a {@link Transition} object from a resource 68 * 69 * @param resource The resource id of the transition to load 70 * @return The loaded Transition object 71 * @throws android.content.res.Resources.NotFoundException when the 72 * transition cannot be loaded 73 */ 74 public Transition inflateTransition(int resource) { 75 XmlResourceParser parser = mContext.getResources().getXml(resource); 76 try { 77 return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null); 78 } catch (XmlPullParserException e) { 79 InflateException ex = new InflateException(e.getMessage()); 80 ex.initCause(e); 81 throw ex; 82 } catch (IOException e) { 83 InflateException ex = new InflateException( 84 parser.getPositionDescription() 85 + ": " + e.getMessage()); 86 ex.initCause(e); 87 throw ex; 88 } finally { 89 parser.close(); 90 } 91 } 92 93 /** 94 * Loads a {@link TransitionManager} object from a resource 95 * 96 * @param resource The resource id of the transition manager to load 97 * @return The loaded TransitionManager object 98 * @throws android.content.res.Resources.NotFoundException when the 99 * transition manager cannot be loaded 100 */ 101 public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) { 102 XmlResourceParser parser = mContext.getResources().getXml(resource); 103 try { 104 return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot); 105 } catch (XmlPullParserException e) { 106 InflateException ex = new InflateException(e.getMessage()); 107 ex.initCause(e); 108 throw ex; 109 } catch (IOException e) { 110 InflateException ex = new InflateException( 111 parser.getPositionDescription() 112 + ": " + e.getMessage()); 113 ex.initCause(e); 114 throw ex; 115 } finally { 116 parser.close(); 117 } 118 } 119 120 // 121 // Transition loading 122 // 123 private Transition createTransitionFromXml(XmlPullParser parser, 124 AttributeSet attrs, Transition parent) 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 TransitionSet transitionSet = (parent instanceof TransitionSet) 134 ? (TransitionSet) parent : null; 135 136 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 137 && type != XmlPullParser.END_DOCUMENT) { 138 139 if (type != XmlPullParser.START_TAG) { 140 continue; 141 } 142 143 String name = parser.getName(); 144 if ("fade".equals(name)) { 145 transition = new Fade(mContext, attrs); 146 } else if ("changeBounds".equals(name)) { 147 transition = new ChangeBounds(mContext, attrs); 148 } else if ("slide".equals(name)) { 149 transition = new Slide(mContext, attrs); 150 } else if ("explode".equals(name)) { 151 transition = new Explode(mContext, attrs); 152 } else if ("changeImageTransform".equals(name)) { 153 transition = new ChangeImageTransform(mContext, attrs); 154 } else if ("changeTransform".equals(name)) { 155 transition = new ChangeTransform(mContext, attrs); 156 } else if ("changeClipBounds".equals(name)) { 157 transition = new ChangeClipBounds(mContext, attrs); 158 } else if ("autoTransition".equals(name)) { 159 transition = new AutoTransition(mContext, attrs); 160 } else if ("recolor".equals(name)) { 161 transition = new Recolor(mContext, attrs); 162 } else if ("transitionSet".equals(name)) { 163 transition = new TransitionSet(mContext, attrs); 164 } else if ("transition".equals(name)) { 165 transition = (Transition) createCustom(attrs, Transition.class, "transition"); 166 } else if ("targets".equals(name)) { 167 getTargetIds(parser, attrs, parent); 168 } else if ("arcMotion".equals(name)) { 169 parent.setPathMotion(new ArcMotion(mContext, attrs)); 170 } else if ("pathMotion".equals(name)) { 171 parent.setPathMotion((PathMotion)createCustom(attrs, PathMotion.class, "pathMotion")); 172 } else if ("patternMotion".equals(name)) { 173 parent.setPathMotion(new PatternMotion(mContext, attrs)); 174 } else { 175 throw new RuntimeException("Unknown scene name: " + parser.getName()); 176 } 177 if (transition != null) { 178 if (!parser.isEmptyElementTag()) { 179 createTransitionFromXml(parser, attrs, transition); 180 } 181 if (transitionSet != null) { 182 transitionSet.addTransition(transition); 183 transition = null; 184 } else if (parent != null) { 185 throw new InflateException("Could not add transition to another transition."); 186 } 187 } 188 } 189 190 return transition; 191 } 192 193 private Object createCustom(AttributeSet attrs, Class expectedType, String tag) { 194 String className = attrs.getAttributeValue(null, "class"); 195 196 if (className == null) { 197 throw new InflateException(tag + " tag must have a 'class' attribute"); 198 } 199 200 try { 201 synchronized (sConstructors) { 202 Constructor constructor = sConstructors.get(className); 203 if (constructor == null) { 204 Class c = mContext.getClassLoader().loadClass(className) 205 .asSubclass(expectedType); 206 if (c != null) { 207 constructor = c.getConstructor(sConstructorSignature); 208 sConstructors.put(className, constructor); 209 } 210 } 211 212 return constructor.newInstance(mContext, attrs); 213 } 214 } catch (InstantiationException e) { 215 throw new InflateException("Could not instantiate " + expectedType + " class " + 216 className, e); 217 } catch (ClassNotFoundException e) { 218 throw new InflateException("Could not instantiate " + expectedType + " class " + 219 className, e); 220 } catch (InvocationTargetException e) { 221 throw new InflateException("Could not instantiate " + expectedType + " class " + 222 className, e); 223 } catch (NoSuchMethodException e) { 224 throw new InflateException("Could not instantiate " + expectedType + " class " + 225 className, e); 226 } catch (IllegalAccessException e) { 227 throw new InflateException("Could not instantiate " + expectedType + " class " + 228 className, e); 229 } 230 } 231 232 private void getTargetIds(XmlPullParser parser, 233 AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException { 234 235 // Make sure we are on a start tag. 236 int type; 237 int depth = parser.getDepth(); 238 239 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 240 && type != XmlPullParser.END_DOCUMENT) { 241 242 if (type != XmlPullParser.START_TAG) { 243 continue; 244 } 245 246 String name = parser.getName(); 247 if (name.equals("target")) { 248 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TransitionTarget); 249 int id = a.getResourceId(R.styleable.TransitionTarget_targetId, 0); 250 String transitionName; 251 if (id != 0) { 252 transition.addTarget(id); 253 } else if ((id = a.getResourceId(R.styleable.TransitionTarget_excludeId, 0)) != 0) { 254 transition.excludeTarget(id, true); 255 } else if ((transitionName = a.getString(R.styleable.TransitionTarget_targetName)) 256 != null) { 257 transition.addTarget(transitionName); 258 } else if ((transitionName = a.getString(R.styleable.TransitionTarget_excludeName)) 259 != null) { 260 transition.excludeTarget(transitionName, true); 261 } else { 262 String className = a.getString(R.styleable.TransitionTarget_excludeClass); 263 try { 264 if (className != null) { 265 Class clazz = Class.forName(className); 266 transition.excludeTarget(clazz, true); 267 } else if ((className = 268 a.getString(R.styleable.TransitionTarget_targetClass)) != null) { 269 Class clazz = Class.forName(className); 270 transition.addTarget(clazz); 271 } 272 } catch (ClassNotFoundException e) { 273 throw new RuntimeException("Could not create " + className, e); 274 } 275 } 276 } else { 277 throw new RuntimeException("Unknown scene name: " + parser.getName()); 278 } 279 } 280 } 281 282 // 283 // TransitionManager loading 284 // 285 286 private TransitionManager createTransitionManagerFromXml(XmlPullParser parser, 287 AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException { 288 289 // Make sure we are on a start tag. 290 int type; 291 int depth = parser.getDepth(); 292 TransitionManager transitionManager = null; 293 294 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 295 && type != XmlPullParser.END_DOCUMENT) { 296 297 if (type != XmlPullParser.START_TAG) { 298 continue; 299 } 300 301 String name = parser.getName(); 302 if (name.equals("transitionManager")) { 303 transitionManager = new TransitionManager(); 304 } else if (name.equals("transition") && (transitionManager != null)) { 305 loadTransition(attrs, sceneRoot, transitionManager); 306 } else { 307 throw new RuntimeException("Unknown scene name: " + parser.getName()); 308 } 309 } 310 return transitionManager; 311 } 312 313 private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot, 314 TransitionManager transitionManager) throws Resources.NotFoundException { 315 316 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TransitionManager); 317 int transitionId = a.getResourceId(R.styleable.TransitionManager_transition, -1); 318 int fromId = a.getResourceId(R.styleable.TransitionManager_fromScene, -1); 319 Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext); 320 int toId = a.getResourceId(R.styleable.TransitionManager_toScene, -1); 321 Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext); 322 323 if (transitionId >= 0) { 324 Transition transition = inflateTransition(transitionId); 325 if (transition != null) { 326 if (toScene == null) { 327 throw new RuntimeException("No toScene for transition ID " + transitionId); 328 } 329 if (fromScene == null) { 330 transitionManager.setTransition(toScene, transition); 331 } else { 332 transitionManager.setTransition(fromScene, toScene, transition); 333 } 334 } 335 } 336 a.recycle(); 337 } 338} 339