TransitionInflater.java revision d6107a3170df61d9e776fcd5666acfc9135c6f16
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 ("autoTransition".equals(name)) {
157                transition = new AutoTransition();
158                newTransition = true;
159            } else if ("recolor".equals(name)) {
160                transition = new Recolor();
161                newTransition = true;
162            } else if ("transitionSet".equals(name)) {
163                transition = new TransitionSet();
164                TypedArray a = mContext.obtainStyledAttributes(attrs,
165                        com.android.internal.R.styleable.TransitionSet);
166                int ordering = a.getInt(
167                        com.android.internal.R.styleable.TransitionSet_transitionOrdering,
168                        TransitionSet.ORDERING_TOGETHER);
169                ((TransitionSet) transition).setOrdering(ordering);
170                createTransitionFromXml(parser, attrs, ((TransitionSet) transition));
171                a.recycle();
172                newTransition = true;
173            } else if ("targets".equals(name)) {
174                if (parser.getDepth() - 1 > depth && transition != null) {
175                    // We're inside the child tag - add targets to the child
176                    getTargetIds(parser, attrs, transition);
177                } else if (parser.getDepth() - 1 == depth && transitionSet != null) {
178                    // add targets to the set
179                    getTargetIds(parser, attrs, transitionSet);
180                }
181            }
182            if (transition != null || "targets".equals(name)) {
183                if (newTransition) {
184                    loadTransition(transition, attrs);
185                    if (transitionSet != null) {
186                        transitionSet.addTransition(transition);
187                    }
188                }
189            } else {
190                throw new RuntimeException("Unknown scene name: " + parser.getName());
191            }
192        }
193
194        return transition;
195    }
196
197    private Slide createSlideTransition(AttributeSet attrs) {
198        TypedArray a = mContext.obtainStyledAttributes(attrs,
199                com.android.internal.R.styleable.Slide);
200        int edge = a.getInt(com.android.internal.R.styleable.Slide_slideEdge, Slide.BOTTOM);
201        Slide slide = new Slide(edge);
202        a.recycle();
203        return slide;
204    }
205
206    private void getTargetIds(XmlPullParser parser,
207            AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
208
209        // Make sure we are on a start tag.
210        int type;
211        int depth = parser.getDepth();
212
213        ArrayList<Integer> targetIds = new ArrayList<Integer>();
214        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
215                && type != XmlPullParser.END_DOCUMENT) {
216
217            if (type != XmlPullParser.START_TAG) {
218                continue;
219            }
220
221            String  name = parser.getName();
222            if (name.equals("target")) {
223                TypedArray a = mContext.obtainStyledAttributes(attrs,
224                        com.android.internal.R.styleable.TransitionTarget);
225                int id = a.getResourceId(
226                        com.android.internal.R.styleable.TransitionTarget_targetId, -1);
227                if (id >= 0) {
228                    targetIds.add(id);
229                }
230            } else {
231                throw new RuntimeException("Unknown scene name: " + parser.getName());
232            }
233        }
234        int numTargets = targetIds.size();
235        if (numTargets > 0) {
236            for (int i = 0; i < numTargets; ++i) {
237                transition.addTarget(targetIds.get(i));
238            }
239        }
240    }
241
242    private Transition loadTransition(Transition transition, AttributeSet attrs)
243            throws Resources.NotFoundException {
244
245        TypedArray a =
246                mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition);
247        long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1);
248        if (duration >= 0) {
249            transition.setDuration(duration);
250        }
251        long startDelay = a.getInt(com.android.internal.R.styleable.Transition_startDelay, -1);
252        if (startDelay > 0) {
253            transition.setStartDelay(startDelay);
254        }
255        final int resID =
256                a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
257        if (resID > 0) {
258            transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID));
259        }
260        a.recycle();
261        return transition;
262    }
263
264    //
265    // TransitionManager loading
266    //
267
268    private TransitionManager createTransitionManagerFromXml(XmlPullParser parser,
269            AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException {
270
271        // Make sure we are on a start tag.
272        int type;
273        int depth = parser.getDepth();
274        TransitionManager transitionManager = null;
275
276        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
277                && type != XmlPullParser.END_DOCUMENT) {
278
279            if (type != XmlPullParser.START_TAG) {
280                continue;
281            }
282
283            String  name = parser.getName();
284            if (name.equals("transitionManager")) {
285                transitionManager = new TransitionManager();
286            } else if (name.equals("transition") && (transitionManager != null)) {
287                loadTransition(attrs, sceneRoot, transitionManager);
288            } else {
289                throw new RuntimeException("Unknown scene name: " + parser.getName());
290            }
291        }
292        return transitionManager;
293    }
294
295    private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot,
296            TransitionManager transitionManager) throws Resources.NotFoundException {
297
298        TypedArray a = mContext.obtainStyledAttributes(attrs,
299                com.android.internal.R.styleable.TransitionManager);
300        int transitionId = a.getResourceId(
301                com.android.internal.R.styleable.TransitionManager_transition, -1);
302        int fromId = a.getResourceId(
303                com.android.internal.R.styleable.TransitionManager_fromScene, -1);
304        Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext);
305        int toId = a.getResourceId(
306                com.android.internal.R.styleable.TransitionManager_toScene, -1);
307        Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext);
308
309        if (transitionId >= 0) {
310            Transition transition = inflateTransition(transitionId);
311            if (transition != null) {
312                if (fromScene == null) {
313                    if (toScene == null) {
314                        throw new RuntimeException("No matching fromScene or toScene " +
315                                "for transition ID " + transitionId);
316                    } else {
317                        transitionManager.setTransition(toScene, transition);
318                    }
319                } else if (toScene == null) {
320                    transitionManager.setExitTransition(fromScene, transition);
321                } else {
322                    transitionManager.setTransition(fromScene, toScene, transition);
323                }
324            }
325        }
326        a.recycle();
327    }
328}
329