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