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