• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.support.transition;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.content.res.TypedArray;
22 import android.content.res.XmlResourceParser;
23 import android.support.annotation.NonNull;
24 import android.support.v4.content.res.TypedArrayUtils;
25 import android.support.v4.util.ArrayMap;
26 import android.util.AttributeSet;
27 import android.util.Xml;
28 import android.view.InflateException;
29 import android.view.ViewGroup;
30 
31 import org.xmlpull.v1.XmlPullParser;
32 import org.xmlpull.v1.XmlPullParserException;
33 
34 import java.io.IOException;
35 import java.lang.reflect.Constructor;
36 
37 /**
38  * This class inflates scenes and transitions from resource files.
39  */
40 public 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 
TransitionInflater(@onNull Context context)48     private TransitionInflater(@NonNull Context context) {
49         mContext = context;
50     }
51 
52     /**
53      * Obtains the TransitionInflater from the given context.
54      */
from(Context context)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      */
inflateTransition(int resource)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      */
inflateTransitionManager(int resource, ViewGroup sceneRoot)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     //
createTransitionFromXml(XmlPullParser parser, AttributeSet attrs, Transition parent)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             String name = parser.getName();
132             if ("fade".equals(name)) {
133                 transition = new Fade(mContext, attrs);
134             } else if ("changeBounds".equals(name)) {
135                 transition = new ChangeBounds(mContext, attrs);
136             } else if ("slide".equals(name)) {
137                 transition = new Slide(mContext, attrs);
138             } else if ("explode".equals(name)) {
139                 transition = new Explode(mContext, attrs);
140             } else if ("changeImageTransform".equals(name)) {
141                 transition = new ChangeImageTransform(mContext, attrs);
142             } else if ("changeTransform".equals(name)) {
143                 transition = new ChangeTransform(mContext, attrs);
144             } else if ("changeClipBounds".equals(name)) {
145                 transition = new ChangeClipBounds(mContext, attrs);
146             } else if ("autoTransition".equals(name)) {
147                 transition = new AutoTransition(mContext, attrs);
148             } else if ("changeScroll".equals(name)) {
149                 transition = new ChangeScroll(mContext, attrs);
150             } else if ("transitionSet".equals(name)) {
151                 transition = new TransitionSet(mContext, attrs);
152             } else if ("transition".equals(name)) {
153                 transition = (Transition) createCustom(attrs, Transition.class, "transition");
154             } else if ("targets".equals(name)) {
155                 getTargetIds(parser, attrs, parent);
156             } else if ("arcMotion".equals(name)) {
157                 if (parent == null) {
158                     throw new RuntimeException("Invalid use of arcMotion element");
159                 }
160                 parent.setPathMotion(new ArcMotion(mContext, attrs));
161             } else if ("pathMotion".equals(name)) {
162                 if (parent == null) {
163                     throw new RuntimeException("Invalid use of pathMotion element");
164                 }
165                 parent.setPathMotion((PathMotion) createCustom(attrs, PathMotion.class,
166                         "pathMotion"));
167             } else if ("patternPathMotion".equals(name)) {
168                 if (parent == null) {
169                     throw new RuntimeException("Invalid use of patternPathMotion element");
170                 }
171                 parent.setPathMotion(new PatternPathMotion(mContext, attrs));
172             } else {
173                 throw new RuntimeException("Unknown scene name: " + parser.getName());
174             }
175             if (transition != null) {
176                 if (!parser.isEmptyElementTag()) {
177                     createTransitionFromXml(parser, attrs, transition);
178                 }
179                 if (transitionSet != null) {
180                     transitionSet.addTransition(transition);
181                     transition = null;
182                 } else if (parent != null) {
183                     throw new InflateException("Could not add transition to another transition.");
184                 }
185             }
186         }
187 
188         return transition;
189     }
190 
createCustom(AttributeSet attrs, Class expectedType, String tag)191     private Object createCustom(AttributeSet attrs, Class expectedType, String tag) {
192         String className = attrs.getAttributeValue(null, "class");
193 
194         if (className == null) {
195             throw new InflateException(tag + " tag must have a 'class' attribute");
196         }
197 
198         try {
199             synchronized (CONSTRUCTORS) {
200                 Constructor constructor = CONSTRUCTORS.get(className);
201                 if (constructor == null) {
202                     @SuppressWarnings("unchecked")
203                     Class<?> c = mContext.getClassLoader().loadClass(className)
204                             .asSubclass(expectedType);
205                     if (c != null) {
206                         constructor = c.getConstructor(CONSTRUCTOR_SIGNATURE);
207                         constructor.setAccessible(true);
208                         CONSTRUCTORS.put(className, constructor);
209                     }
210                 }
211                 //noinspection ConstantConditions
212                 return constructor.newInstance(mContext, attrs);
213             }
214         } catch (Exception e) {
215             throw new InflateException("Could not instantiate " + expectedType + " class "
216                     + className, e);
217         }
218     }
219 
getTargetIds(XmlPullParser parser, AttributeSet attrs, Transition transition)220     private void getTargetIds(XmlPullParser parser,
221             AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
222 
223         // Make sure we are on a start tag.
224         int type;
225         int depth = parser.getDepth();
226 
227         while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
228                 && type != XmlPullParser.END_DOCUMENT) {
229 
230             if (type != XmlPullParser.START_TAG) {
231                 continue;
232             }
233 
234             String name = parser.getName();
235             if (name.equals("target")) {
236                 TypedArray a = mContext.obtainStyledAttributes(attrs, Styleable.TRANSITION_TARGET);
237                 int id = TypedArrayUtils.getNamedResourceId(a, parser, "targetId",
238                         Styleable.TransitionTarget.TARGET_ID, 0);
239                 String transitionName;
240                 if (id != 0) {
241                     transition.addTarget(id);
242                 } else if ((id = TypedArrayUtils.getNamedResourceId(a, parser, "excludeId",
243                         Styleable.TransitionTarget.EXCLUDE_ID, 0)) != 0) {
244                     transition.excludeTarget(id, true);
245                 } else if ((transitionName = TypedArrayUtils.getNamedString(a, parser, "targetName",
246                         Styleable.TransitionTarget.TARGET_NAME)) != null) {
247                     transition.addTarget(transitionName);
248                 } else if ((transitionName = TypedArrayUtils.getNamedString(a, parser,
249                         "excludeName", Styleable.TransitionTarget.EXCLUDE_NAME)) != null) {
250                     transition.excludeTarget(transitionName, true);
251                 } else {
252                     String className = TypedArrayUtils.getNamedString(a, parser,
253                             "excludeClass", Styleable.TransitionTarget.EXCLUDE_CLASS);
254                     try {
255                         if (className != null) {
256                             Class clazz = Class.forName(className);
257                             transition.excludeTarget(clazz, true);
258                         } else if ((className = TypedArrayUtils.getNamedString(a, parser,
259                                 "targetClass", Styleable.TransitionTarget.TARGET_CLASS)) != null) {
260                             Class clazz = Class.forName(className);
261                             transition.addTarget(clazz);
262                         }
263                     } catch (ClassNotFoundException e) {
264                         a.recycle();
265                         throw new RuntimeException("Could not create " + className, e);
266                     }
267                 }
268                 a.recycle();
269             } else {
270                 throw new RuntimeException("Unknown scene name: " + parser.getName());
271             }
272         }
273     }
274 
275     //
276     // TransitionManager loading
277     //
278 
createTransitionManagerFromXml(XmlPullParser parser, AttributeSet attrs, ViewGroup sceneRoot)279     private TransitionManager createTransitionManagerFromXml(XmlPullParser parser,
280             AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException {
281 
282         // Make sure we are on a start tag.
283         int type;
284         int depth = parser.getDepth();
285         TransitionManager transitionManager = null;
286 
287         while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
288                 && type != XmlPullParser.END_DOCUMENT) {
289 
290             if (type != XmlPullParser.START_TAG) {
291                 continue;
292             }
293 
294             String name = parser.getName();
295             if (name.equals("transitionManager")) {
296                 transitionManager = new TransitionManager();
297             } else if (name.equals("transition") && (transitionManager != null)) {
298                 loadTransition(attrs, parser, sceneRoot, transitionManager);
299             } else {
300                 throw new RuntimeException("Unknown scene name: " + parser.getName());
301             }
302         }
303         return transitionManager;
304     }
305 
loadTransition(AttributeSet attrs, XmlPullParser parser, ViewGroup sceneRoot, TransitionManager transitionManager)306     private void loadTransition(AttributeSet attrs, XmlPullParser parser, ViewGroup sceneRoot,
307             TransitionManager transitionManager) throws Resources.NotFoundException {
308 
309         TypedArray a = mContext.obtainStyledAttributes(attrs, Styleable.TRANSITION_MANAGER);
310         int transitionId = TypedArrayUtils.getNamedResourceId(a, parser, "transition",
311                 Styleable.TransitionManager.TRANSITION, -1);
312         int fromId = TypedArrayUtils.getNamedResourceId(a, parser, "fromScene",
313                 Styleable.TransitionManager.FROM_SCENE, -1);
314         Scene fromScene = (fromId < 0) ? null : Scene.getSceneForLayout(sceneRoot, fromId,
315                 mContext);
316         int toId = TypedArrayUtils.getNamedResourceId(a, parser, "toScene",
317                 Styleable.TransitionManager.TO_SCENE, -1);
318         Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext);
319 
320         if (transitionId >= 0) {
321             Transition transition = inflateTransition(transitionId);
322             if (transition != null) {
323                 if (toScene == null) {
324                     throw new RuntimeException("No toScene for transition ID " + transitionId);
325                 }
326                 if (fromScene == null) {
327                     transitionManager.setTransition(toScene, transition);
328                 } else {
329                     transitionManager.setTransition(fromScene, toScene, transition);
330                 }
331             }
332         }
333         a.recycle();
334     }
335 
336 }
337