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