blob: be658af0c86f8f244055351a2aae5360ffba0bae [file] [log] [blame]
Chet Haasefaebd8f2012-05-18 14:17:57 -07001/*
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 */
Chet Haase6ebe3de2013-06-17 16:50:50 -070016
Chet Haasefaebd8f2012-05-18 14:17:57 -070017package android.view.transition;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.content.res.TypedArray;
22import android.content.res.XmlResourceParser;
Chet Haase08735182013-06-04 10:44:40 -070023import android.util.ArrayMap;
Chet Haasefaebd8f2012-05-18 14:17:57 -070024import 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;
Chet Haasefaebd8f2012-05-18 14:17:57 -070035
36/**
37 * This class inflates scenes and transitions from resource files.
38 */
39public class TransitionInflater {
40
41 // We only need one inflater for any given context. Also, this allows us to associate
42 // ids with unique instances per-Context, used to avoid re-inflating
43 // already-inflated resources into new/different instances
Chet Haase08735182013-06-04 10:44:40 -070044 private static final ArrayMap<Context, TransitionInflater> sInflaterMap =
45 new ArrayMap<Context, TransitionInflater>();
Chet Haasefaebd8f2012-05-18 14:17:57 -070046
47 private Context mContext;
48 // TODO: do we need id maps for transitions and transitionMgrs as well?
49 SparseArray<Scene> mScenes = new SparseArray<Scene>();
50
51 private TransitionInflater(Context context) {
52 mContext = context;
53 }
54
55 /**
56 * Obtains the TransitionInflater from the given context.
57 */
58 public static TransitionInflater from(Context context) {
59 TransitionInflater inflater = sInflaterMap.get(context);
60 if (inflater != null) {
61 return inflater;
62 }
63 inflater = new TransitionInflater(context);
64 sInflaterMap.put(context, inflater);
65 return inflater;
66 }
67
68 /**
69 * Loads a {@link Transition} object from a resource
70 *
71 * @param resource The resource id of the transition to load
72 * @return The loaded Transition object
73 * @throws android.content.res.Resources.NotFoundException when the
74 * transition cannot be loaded
75 */
76 public Transition inflateTransition(int resource) {
77 XmlResourceParser parser = mContext.getResources().getXml(resource);
78 try {
79 return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null);
80 } catch (XmlPullParserException e) {
81 InflateException ex = new InflateException(e.getMessage());
82 ex.initCause(e);
83 throw ex;
84 } catch (IOException e) {
85 InflateException ex = new InflateException(
86 parser.getPositionDescription()
87 + ": " + e.getMessage());
88 ex.initCause(e);
89 throw ex;
90 } finally {
91 parser.close();
92 }
93 }
94
95 /**
96 * Loads a {@link TransitionManager} object from a resource
97 *
98 *
99 *
100 * @param resource The resource id of the transition manager to load
101 * @return The loaded TransitionManager object
102 * @throws android.content.res.Resources.NotFoundException when the
103 * transition manager cannot be loaded
104 */
105 public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) {
106 XmlResourceParser parser = mContext.getResources().getXml(resource);
107 try {
108 return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot);
109 } catch (XmlPullParserException e) {
110 InflateException ex = new InflateException(e.getMessage());
111 ex.initCause(e);
112 throw ex;
113 } catch (IOException e) {
114 InflateException ex = new InflateException(
115 parser.getPositionDescription()
116 + ": " + e.getMessage());
117 ex.initCause(e);
118 throw ex;
119 } finally {
120 parser.close();
121 }
122 }
123
124 /**
125 * Loads a {@link Scene} object from a resource
126 *
127 * @param resource The resource id of the scene to load
128 * @return The loaded Scene object
129 * @throws android.content.res.Resources.NotFoundException when the scene
130 * cannot be loaded
131 */
132 public Scene inflateScene(int resource, ViewGroup parent) {
133 Scene scene = mScenes.get(resource);
134 if (scene != null) {
135 return scene;
136 }
137 XmlResourceParser parser = mContext.getResources().getXml(resource);
138 try {
139 scene = createSceneFromXml(parser, Xml.asAttributeSet(parser), parent);
140 mScenes.put(resource, scene);
141 return scene;
142 } catch (XmlPullParserException e) {
143 InflateException ex = new InflateException(e.getMessage());
144 ex.initCause(e);
145 throw ex;
146 } catch (IOException e) {
147 InflateException ex = new InflateException(
148 parser.getPositionDescription()
149 + ": " + e.getMessage());
150 ex.initCause(e);
151 throw ex;
152 } finally {
153 parser.close();
154 }
155 }
156
157
158 //
159 // Transition loading
160 //
161
162 private Transition createTransitionFromXml(XmlPullParser parser,
163 AttributeSet attrs, TransitionGroup transitionGroup)
164 throws XmlPullParserException, IOException {
165
166 Transition transition = null;
167
168 // Make sure we are on a start tag.
169 int type;
170 int depth = parser.getDepth();
171
172 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
173 && type != XmlPullParser.END_DOCUMENT) {
174
175 boolean newTransition = false;
176
177 if (type != XmlPullParser.START_TAG) {
178 continue;
179 }
180
181 String name = parser.getName();
182 if ("fade".equals(name)) {
183 transition = new Fade();
184 newTransition = true;
185 } else if ("move".equals(name)) {
186 transition = new Move();
187 newTransition = true;
188 } else if ("slide".equals(name)) {
189 transition = new Slide();
190 newTransition = true;
191 } else if ("autoTransition".equals(name)) {
192 transition = new AutoTransition();
193 newTransition = true;
194 } else if ("recolor".equals(name)) {
195 transition = new Recolor();
196 newTransition = true;
197 } else if ("transitionGroup".equals(name)) {
198 transition = new TransitionGroup();
199 createTransitionFromXml(parser, attrs, ((TransitionGroup) transition));
200 newTransition = true;
201 } else if ("targets".equals(name)) {
202 if (parser.getDepth() - 1 > depth && transition != null) {
203 // We're inside the child tag - add targets to the child
204 getTargetIDs(parser, attrs, transition);
205 } else if (parser.getDepth() - 1 == depth && transitionGroup != null) {
206 // add targets to the group
207 getTargetIDs(parser, attrs, transitionGroup);
208 }
209 }
210 if (transition != null || "targets".equals(name)) {
211 if (newTransition) {
212 loadTransition(transition, attrs);
213 if (transitionGroup != null) {
214 transitionGroup.addTransitions(transition);
215 }
216 }
217 } else {
218 throw new RuntimeException("Unknown scene name: " + parser.getName());
219 }
220 }
221
222 return transition;
223 }
224
225 private void getTargetIDs(XmlPullParser parser,
226 AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
227
228 // Make sure we are on a start tag.
229 int type;
230 int depth = parser.getDepth();
231
232 ArrayList<Integer> targetIds = new ArrayList<Integer>();
233 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
234 && type != XmlPullParser.END_DOCUMENT) {
235
236 if (type != XmlPullParser.START_TAG) {
237 continue;
238 }
239
240 String name = parser.getName();
241 if (name.equals("target")) {
242 TypedArray a = mContext.obtainStyledAttributes(attrs,
243 com.android.internal.R.styleable.Transition);
244 int id = a.getResourceId(com.android.internal.R.styleable.Transition_targetID, -1);
245 if (id >= 0) {
246 targetIds.add(id);
247 }
248 } else {
249 throw new RuntimeException("Unknown scene name: " + parser.getName());
250 }
251 }
252 int numTargets = targetIds.size();
253 if (numTargets > 0) {
254 int[] targetsArray = new int[numTargets];
255 for (int i = 0; i < targetIds.size(); ++i) {
256 targetsArray[i] = targetIds.get(i);
257 }
258 transition.setTargetIds(targetsArray);
259 }
260 }
261
262 private Transition loadTransition(Transition transition, AttributeSet attrs)
263 throws Resources.NotFoundException {
264
265 TypedArray a =
266 mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition);
267 long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1);
268 if (duration >= 0) {
269 transition.setDuration(duration);
270 }
271 long startOffset = a.getInt(com.android.internal.R.styleable.Transition_startOffset, -1);
272 if (startOffset > 0) {
273 transition.setStartDelay(startOffset);
274 }
275 final int resID =
276 a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
277 if (resID > 0) {
278 transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID));
279 }
280 a.recycle();
281 return transition;
282 }
283
284 //
285 // TransitionManager loading
286 //
287
288 private TransitionManager createTransitionManagerFromXml(XmlPullParser parser,
289 AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException {
290
291 // Make sure we are on a start tag.
292 int type;
293 int depth = parser.getDepth();
294 TransitionManager transitionManager = null;
295
296 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
297 && type != XmlPullParser.END_DOCUMENT) {
298
299 if (type != XmlPullParser.START_TAG) {
300 continue;
301 }
302
303 String name = parser.getName();
304 if (name.equals("transitionManager")) {
305 transitionManager = new TransitionManager();
306 } else if (name.equals("transition") && (transitionManager != null)) {
307 loadTransition(attrs, sceneRoot, transitionManager);
308 } else {
309 throw new RuntimeException("Unknown scene name: " + parser.getName());
310 }
311 }
312 return transitionManager;
313 }
314
315 private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot,
316 TransitionManager transitionManager)
317 throws Resources.NotFoundException {
318
319 TypedArray a = mContext.obtainStyledAttributes(attrs,
320 com.android.internal.R.styleable.TransitionManager);
321 int transitionId = attrs.getAttributeResourceValue(
322 com.android.internal.R.styleable.TransitionManager_transition, -1);
323 Scene fromScene = null, toScene = null;
324 int fromId = attrs.getAttributeResourceValue(
325 com.android.internal.R.styleable.TransitionManager_fromScene, -1);
326 if (fromId >= 0) fromScene = inflateScene(fromId, sceneRoot);
327 int toId = attrs.getAttributeResourceValue(
328 com.android.internal.R.styleable.TransitionManager_toScene, -1);
329 if (toId >= 0) toScene = inflateScene(toId, sceneRoot);
330 if (transitionId >= 0) {
331 Transition transition = inflateTransition(transitionId);
332 if (transition != null) {
333 if (fromScene != null) {
334 if (toScene == null){
335 throw new RuntimeException("No matching toScene for given fromScene " +
336 "for transition ID " + transitionId);
337 } else {
338 transitionManager.setTransition(fromScene, toScene, transition);
339 }
340 } else if (toId >= 0) {
341 transitionManager.setTransition(toScene, transition);
342 }
343 }
344 }
345 a.recycle();
346 }
347
348 //
349 // Scene loading
350 //
351
352 private Scene createSceneFromXml(XmlPullParser parser, AttributeSet attrs, ViewGroup parent)
353 throws XmlPullParserException, IOException {
354 Scene scene = null;
355
356 // Make sure we are on a start tag.
357 int type;
358 int depth = parser.getDepth();
359
360 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
361 && type != XmlPullParser.END_DOCUMENT) {
362
363 if (type != XmlPullParser.START_TAG) {
364 continue;
365 }
366
367 String name = parser.getName();
368 if (name.equals("scene")) {
369 scene = loadScene(attrs, parent);
370 } else {
371 throw new RuntimeException("Unknown scene name: " + parser.getName());
372 }
373 }
374
375 return scene;
376 }
377
378 private Scene loadScene(AttributeSet attrs, ViewGroup parent)
379 throws Resources.NotFoundException {
380
381 Scene scene;
382 TypedArray a = mContext.obtainStyledAttributes(attrs,
383 com.android.internal.R.styleable.Scene);
384 int layoutId = a.getResourceId(com.android.internal.R.styleable.Scene_layout, -1);
385 if (layoutId >= 0) {
386 scene = new Scene(parent, layoutId, mContext);
387 } else {
388 scene = new Scene(parent);
389 }
390 a.recycle();
391 return scene;
392 }
393}