TransitionInflater.java revision 8158051cdfef95fc1f22b56bba93b9c610f5ecb1
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 ("autoTransition".equals(name)) { 138 transition = new AutoTransition(mContext, attrs); 139 } else if ("transitionSet".equals(name)) { 140 transition = new TransitionSet(mContext, attrs); 141 } else if ("transition".equals(name)) { 142 transition = (Transition) createCustom(attrs, Transition.class, "transition"); 143 } else if ("targets".equals(name)) { 144 getTargetIds(parser, attrs, parent); 145 } else { 146 throw new RuntimeException("Unknown scene name: " + parser.getName()); 147 } 148 if (transition != null) { 149 if (!parser.isEmptyElementTag()) { 150 createTransitionFromXml(parser, attrs, transition); 151 } 152 if (transitionSet != null) { 153 transitionSet.addTransition(transition); 154 transition = null; 155 } else if (parent != null) { 156 throw new InflateException("Could not add transition to another transition."); 157 } 158 } 159 } 160 161 return transition; 162 } 163 164 private Object createCustom(AttributeSet attrs, Class expectedType, String tag) { 165 String className = attrs.getAttributeValue(null, "class"); 166 167 if (className == null) { 168 throw new InflateException(tag + " tag must have a 'class' attribute"); 169 } 170 171 try { 172 synchronized (CONSTRUCTORS) { 173 Constructor constructor = CONSTRUCTORS.get(className); 174 if (constructor == null) { 175 Class<?> c = mContext.getClassLoader().loadClass(className) 176 .asSubclass(expectedType); 177 if (c != null) { 178 constructor = c.getConstructor(CONSTRUCTOR_SIGNATURE); 179 constructor.setAccessible(true); 180 CONSTRUCTORS.put(className, constructor); 181 } 182 } 183 //noinspection ConstantConditions 184 return constructor.newInstance(mContext, attrs); 185 } 186 } catch (Exception e) { 187 throw new InflateException("Could not instantiate " + expectedType + " class " 188 + className, e); 189 } 190 } 191 192 private void getTargetIds(XmlPullParser parser, 193 AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException { 194 195 // Make sure we are on a start tag. 196 int type; 197 int depth = parser.getDepth(); 198 199 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 200 && type != XmlPullParser.END_DOCUMENT) { 201 202 if (type != XmlPullParser.START_TAG) { 203 continue; 204 } 205 206 String name = parser.getName(); 207 if (name.equals("target")) { 208 TypedArray a = mContext.obtainStyledAttributes(attrs, Styleable.TRANSITION_TARGET); 209 int id = TypedArrayUtils.getNamedResourceId(a, parser, "targetId", 210 Styleable.TransitionTarget.TARGET_ID, 0); 211 String transitionName; 212 if (id != 0) { 213 transition.addTarget(id); 214 } else if ((id = TypedArrayUtils.getNamedResourceId(a, parser, "excludeId", 215 Styleable.TransitionTarget.EXCLUDE_ID, 0)) != 0) { 216 transition.excludeTarget(id, true); 217 } else if ((transitionName = TypedArrayUtils.getNamedString(a, parser, "targetName", 218 Styleable.TransitionTarget.TARGET_NAME)) != null) { 219 transition.addTarget(transitionName); 220 } else if ((transitionName = TypedArrayUtils.getNamedString(a, parser, 221 "excludeName", Styleable.TransitionTarget.EXCLUDE_NAME)) != null) { 222 transition.excludeTarget(transitionName, true); 223 } else { 224 String className = TypedArrayUtils.getNamedString(a, parser, 225 "excludeClass", Styleable.TransitionTarget.EXCLUDE_CLASS); 226 try { 227 if (className != null) { 228 Class clazz = Class.forName(className); 229 transition.excludeTarget(clazz, true); 230 } else if ((className = TypedArrayUtils.getNamedString(a, parser, 231 "targetClass", Styleable.TransitionTarget.TARGET_CLASS)) != null) { 232 Class clazz = Class.forName(className); 233 transition.addTarget(clazz); 234 } 235 } catch (ClassNotFoundException e) { 236 a.recycle(); 237 throw new RuntimeException("Could not create " + className, e); 238 } 239 } 240 a.recycle(); 241 } else { 242 throw new RuntimeException("Unknown scene name: " + parser.getName()); 243 } 244 } 245 } 246 247 // 248 // TransitionManager loading 249 // 250 251 private TransitionManager createTransitionManagerFromXml(XmlPullParser parser, 252 AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException { 253 254 // Make sure we are on a start tag. 255 int type; 256 int depth = parser.getDepth(); 257 TransitionManager transitionManager = null; 258 259 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 260 && type != XmlPullParser.END_DOCUMENT) { 261 262 if (type != XmlPullParser.START_TAG) { 263 continue; 264 } 265 266 String name = parser.getName(); 267 if (name.equals("transitionManager")) { 268 transitionManager = new TransitionManager(); 269 } else if (name.equals("transition") && (transitionManager != null)) { 270 loadTransition(attrs, parser, sceneRoot, transitionManager); 271 } else { 272 throw new RuntimeException("Unknown scene name: " + parser.getName()); 273 } 274 } 275 return transitionManager; 276 } 277 278 private void loadTransition(AttributeSet attrs, XmlPullParser parser, ViewGroup sceneRoot, 279 TransitionManager transitionManager) throws Resources.NotFoundException { 280 281 TypedArray a = mContext.obtainStyledAttributes(attrs, Styleable.TRANSITION_MANAGER); 282 int transitionId = TypedArrayUtils.getNamedResourceId(a, parser, "transition", 283 Styleable.TransitionManager.TRANSITION, -1); 284 int fromId = TypedArrayUtils.getNamedResourceId(a, parser, "fromScene", 285 Styleable.TransitionManager.FROM_SCENE, -1); 286 Scene fromScene = (fromId < 0) ? null : Scene.getSceneForLayout(sceneRoot, fromId, 287 mContext); 288 int toId = TypedArrayUtils.getNamedResourceId(a, parser, "toScene", 289 Styleable.TransitionManager.TO_SCENE, -1); 290 Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext); 291 292 if (transitionId >= 0) { 293 Transition transition = inflateTransition(transitionId); 294 if (transition != null) { 295 if (toScene == null) { 296 throw new RuntimeException("No toScene for transition ID " + transitionId); 297 } 298 if (fromScene == null) { 299 transitionManager.setTransition(toScene, transition); 300 } else { 301 transitionManager.setTransition(fromScene, toScene, transition); 302 } 303 } 304 } 305 a.recycle(); 306 } 307 308} 309