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