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