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 com.android.internal.R; 20 21 import org.xmlpull.v1.XmlPullParser; 22 import org.xmlpull.v1.XmlPullParserException; 23 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.content.res.XmlResourceParser; 28 import android.util.ArrayMap; 29 import android.util.AttributeSet; 30 import android.util.Xml; 31 import android.view.InflateException; 32 import android.view.ViewGroup; 33 34 import java.io.IOException; 35 import java.lang.reflect.Constructor; 36 import 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 */ 46 public 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 TransitionInflater(Context context)55 private TransitionInflater(Context context) { 56 mContext = context; 57 } 58 59 /** 60 * Obtains the TransitionInflater from the given context. 61 */ from(Context context)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 */ inflateTransition(int resource)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 */ inflateTransitionManager(int resource, ViewGroup sceneRoot)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 // createTransitionFromXml(XmlPullParser parser, AttributeSet attrs, Transition parent)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 ("changeScroll".equals(name)) { 163 transition = new ChangeScroll(mContext, attrs); 164 } else if ("transitionSet".equals(name)) { 165 transition = new TransitionSet(mContext, attrs); 166 } else if ("transition".equals(name)) { 167 transition = (Transition) createCustom(attrs, Transition.class, "transition"); 168 } else if ("targets".equals(name)) { 169 getTargetIds(parser, attrs, parent); 170 } else if ("arcMotion".equals(name)) { 171 parent.setPathMotion(new ArcMotion(mContext, attrs)); 172 } else if ("pathMotion".equals(name)) { 173 parent.setPathMotion((PathMotion)createCustom(attrs, PathMotion.class, "pathMotion")); 174 } else if ("patternPathMotion".equals(name)) { 175 parent.setPathMotion(new PatternPathMotion(mContext, attrs)); 176 } else { 177 throw new RuntimeException("Unknown scene name: " + parser.getName()); 178 } 179 if (transition != null) { 180 if (!parser.isEmptyElementTag()) { 181 createTransitionFromXml(parser, attrs, transition); 182 } 183 if (transitionSet != null) { 184 transitionSet.addTransition(transition); 185 transition = null; 186 } else if (parent != null) { 187 throw new InflateException("Could not add transition to another transition."); 188 } 189 } 190 } 191 192 return transition; 193 } 194 createCustom(AttributeSet attrs, Class expectedType, String tag)195 private Object createCustom(AttributeSet attrs, Class expectedType, String tag) { 196 String className = attrs.getAttributeValue(null, "class"); 197 198 if (className == null) { 199 throw new InflateException(tag + " tag must have a 'class' attribute"); 200 } 201 202 try { 203 synchronized (sConstructors) { 204 Constructor constructor = sConstructors.get(className); 205 if (constructor == null) { 206 Class c = mContext.getClassLoader().loadClass(className) 207 .asSubclass(expectedType); 208 if (c != null) { 209 constructor = c.getConstructor(sConstructorSignature); 210 sConstructors.put(className, constructor); 211 } 212 } 213 214 return constructor.newInstance(mContext, attrs); 215 } 216 } catch (InstantiationException e) { 217 throw new InflateException("Could not instantiate " + expectedType + " class " + 218 className, e); 219 } catch (ClassNotFoundException e) { 220 throw new InflateException("Could not instantiate " + expectedType + " class " + 221 className, e); 222 } catch (InvocationTargetException e) { 223 throw new InflateException("Could not instantiate " + expectedType + " class " + 224 className, e); 225 } catch (NoSuchMethodException e) { 226 throw new InflateException("Could not instantiate " + expectedType + " class " + 227 className, e); 228 } catch (IllegalAccessException e) { 229 throw new InflateException("Could not instantiate " + expectedType + " class " + 230 className, e); 231 } 232 } 233 getTargetIds(XmlPullParser parser, AttributeSet attrs, Transition transition)234 private void getTargetIds(XmlPullParser parser, 235 AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException { 236 237 // Make sure we are on a start tag. 238 int type; 239 int depth = parser.getDepth(); 240 241 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 242 && type != XmlPullParser.END_DOCUMENT) { 243 244 if (type != XmlPullParser.START_TAG) { 245 continue; 246 } 247 248 String name = parser.getName(); 249 if (name.equals("target")) { 250 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TransitionTarget); 251 int id = a.getResourceId(R.styleable.TransitionTarget_targetId, 0); 252 String transitionName; 253 if (id != 0) { 254 transition.addTarget(id); 255 } else if ((id = a.getResourceId(R.styleable.TransitionTarget_excludeId, 0)) != 0) { 256 transition.excludeTarget(id, true); 257 } else if ((transitionName = a.getString(R.styleable.TransitionTarget_targetName)) 258 != null) { 259 transition.addTarget(transitionName); 260 } else if ((transitionName = a.getString(R.styleable.TransitionTarget_excludeName)) 261 != null) { 262 transition.excludeTarget(transitionName, true); 263 } else { 264 String className = a.getString(R.styleable.TransitionTarget_excludeClass); 265 try { 266 if (className != null) { 267 Class clazz = Class.forName(className); 268 transition.excludeTarget(clazz, true); 269 } else if ((className = 270 a.getString(R.styleable.TransitionTarget_targetClass)) != null) { 271 Class clazz = Class.forName(className); 272 transition.addTarget(clazz); 273 } 274 } catch (ClassNotFoundException e) { 275 throw new RuntimeException("Could not create " + className, e); 276 } 277 } 278 } else { 279 throw new RuntimeException("Unknown scene name: " + parser.getName()); 280 } 281 } 282 } 283 284 // 285 // TransitionManager loading 286 // 287 createTransitionManagerFromXml(XmlPullParser parser, AttributeSet attrs, ViewGroup sceneRoot)288 private TransitionManager createTransitionManagerFromXml(XmlPullParser parser, 289 AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException { 290 291 // Make sure we are on a start tag. 292 int type; 293 int depth = parser.getDepth(); 294 TransitionManager transitionManager = null; 295 296 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 297 && type != XmlPullParser.END_DOCUMENT) { 298 299 if (type != XmlPullParser.START_TAG) { 300 continue; 301 } 302 303 String name = parser.getName(); 304 if (name.equals("transitionManager")) { 305 transitionManager = new TransitionManager(); 306 } else if (name.equals("transition") && (transitionManager != null)) { 307 loadTransition(attrs, sceneRoot, transitionManager); 308 } else { 309 throw new RuntimeException("Unknown scene name: " + parser.getName()); 310 } 311 } 312 return transitionManager; 313 } 314 loadTransition(AttributeSet attrs, ViewGroup sceneRoot, TransitionManager transitionManager)315 private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot, 316 TransitionManager transitionManager) throws Resources.NotFoundException { 317 318 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TransitionManager); 319 int transitionId = a.getResourceId(R.styleable.TransitionManager_transition, -1); 320 int fromId = a.getResourceId(R.styleable.TransitionManager_fromScene, -1); 321 Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext); 322 int toId = a.getResourceId(R.styleable.TransitionManager_toScene, -1); 323 Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext); 324 325 if (transitionId >= 0) { 326 Transition transition = inflateTransition(transitionId); 327 if (transition != null) { 328 if (toScene == null) { 329 throw new RuntimeException("No toScene for transition ID " + transitionId); 330 } 331 if (fromScene == null) { 332 transitionManager.setTransition(toScene, transition); 333 } else { 334 transitionManager.setTransition(fromScene, toScene, transition); 335 } 336 } 337 } 338 a.recycle(); 339 } 340 } 341