TransitionInflater.java revision a98fb7ab6a17d27395cf2c8e86060af49b861be6
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                if (id >= 0) {
233                    transition.addTarget(id);
234                } else if ((id = a.getResourceId(
235                        com.android.internal.R.styleable.TransitionTarget_excludeId, -1)) >= 0) {
236                    transition.excludeTarget(id, true);
237                } else {
238                    String className = a.getString(
239                            com.android.internal.R.styleable.TransitionTarget_excludeClass);
240                    try {
241                        if (className != null) {
242                            Class clazz = Class.forName(className);
243                            transition.excludeTarget(clazz, true);
244                        } else if ((className = a.getString(
245                                com.android.internal.R.styleable.TransitionTarget_targetClass))
246                                != null) {
247                            Class clazz = Class.forName(className);
248                            transition.addTarget(clazz);
249                        }
250                    } catch (ClassNotFoundException e) {
251                        throw new RuntimeException("Could not create " + className, e);
252                    }
253                }
254            } else {
255                throw new RuntimeException("Unknown scene name: " + parser.getName());
256            }
257        }
258    }
259
260    private Transition loadTransition(Transition transition, AttributeSet attrs)
261            throws Resources.NotFoundException {
262
263        TypedArray a =
264                mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition);
265        long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1);
266        if (duration >= 0) {
267            transition.setDuration(duration);
268        }
269        long startDelay = a.getInt(com.android.internal.R.styleable.Transition_startDelay, -1);
270        if (startDelay > 0) {
271            transition.setStartDelay(startDelay);
272        }
273        final int resID =
274                a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
275        if (resID > 0) {
276            transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID));
277        }
278        a.recycle();
279        return transition;
280    }
281
282    //
283    // TransitionManager loading
284    //
285
286    private TransitionManager createTransitionManagerFromXml(XmlPullParser parser,
287            AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException {
288
289        // Make sure we are on a start tag.
290        int type;
291        int depth = parser.getDepth();
292        TransitionManager transitionManager = null;
293
294        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
295                && type != XmlPullParser.END_DOCUMENT) {
296
297            if (type != XmlPullParser.START_TAG) {
298                continue;
299            }
300
301            String  name = parser.getName();
302            if (name.equals("transitionManager")) {
303                transitionManager = new TransitionManager();
304            } else if (name.equals("transition") && (transitionManager != null)) {
305                loadTransition(attrs, sceneRoot, transitionManager);
306            } else {
307                throw new RuntimeException("Unknown scene name: " + parser.getName());
308            }
309        }
310        return transitionManager;
311    }
312
313    private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot,
314            TransitionManager transitionManager) throws Resources.NotFoundException {
315
316        TypedArray a = mContext.obtainStyledAttributes(attrs,
317                com.android.internal.R.styleable.TransitionManager);
318        int transitionId = a.getResourceId(
319                com.android.internal.R.styleable.TransitionManager_transition, -1);
320        int fromId = a.getResourceId(
321                com.android.internal.R.styleable.TransitionManager_fromScene, -1);
322        Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext);
323        int toId = a.getResourceId(
324                com.android.internal.R.styleable.TransitionManager_toScene, -1);
325        Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext);
326
327        if (transitionId >= 0) {
328            Transition transition = inflateTransition(transitionId);
329            if (transition != null) {
330                if (toScene == null) {
331                    throw new RuntimeException("No toScene for transition ID " + transitionId);
332                }
333                if (fromScene == null) {
334                    transitionManager.setTransition(toScene, transition);
335                } else {
336                    transitionManager.setTransition(fromScene, toScene, transition);
337                }
338            }
339        }
340        a.recycle();
341    }
342}
343