blob: 5b7c7379d093f03aba7a2724a8f3a4005b2211a0 [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 Haased82c8ac2013-08-26 14:20:16 -070017package android.transition;
Chet Haasefaebd8f2012-05-18 14:17:57 -070018
19import android.content.Context;
20import android.content.res.Resources;
21import android.content.res.TypedArray;
22import android.content.res.XmlResourceParser;
23import android.util.AttributeSet;
Chet Haasefaebd8f2012-05-18 14:17:57 -070024import android.util.Xml;
George Mountdc21d3b2014-06-05 09:42:48 -070025import android.view.Gravity;
Chet Haasefaebd8f2012-05-18 14:17:57 -070026import 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;
George Mount7b750622014-05-13 18:08:38 -070034import java.util.StringTokenizer;
Chet Haasefaebd8f2012-05-18 14:17:57 -070035
36/**
37 * This class inflates scenes and transitions from resource files.
Chet Haased82c8ac2013-08-26 14:20:16 -070038 *
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}.
Chet Haasefaebd8f2012-05-18 14:17:57 -070043 */
44public class TransitionInflater {
George Mount7b750622014-05-13 18:08:38 -070045 private static final String MATCH_INSTANCE = "instance";
46 private static final String MATCH_VIEW_NAME = "viewName";
47 private static final String MATCH_ID = "id";
48 private static final String MATCH_ITEM_ID = "itemId";
Chet Haasefaebd8f2012-05-18 14:17:57 -070049
Chet Haasefaebd8f2012-05-18 14:17:57 -070050 private Context mContext;
Chet Haasefaebd8f2012-05-18 14:17:57 -070051
52 private TransitionInflater(Context context) {
53 mContext = context;
54 }
55
56 /**
57 * Obtains the TransitionInflater from the given context.
58 */
59 public static TransitionInflater from(Context context) {
Chet Haase58ad1222013-10-30 06:37:29 -070060 return new TransitionInflater(context);
Chet Haasefaebd8f2012-05-18 14:17:57 -070061 }
62
63 /**
64 * Loads a {@link Transition} object from a resource
65 *
66 * @param resource The resource id of the transition to load
67 * @return The loaded Transition object
68 * @throws android.content.res.Resources.NotFoundException when the
69 * transition cannot be loaded
70 */
71 public Transition inflateTransition(int resource) {
72 XmlResourceParser parser = mContext.getResources().getXml(resource);
73 try {
74 return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null);
75 } catch (XmlPullParserException e) {
76 InflateException ex = new InflateException(e.getMessage());
77 ex.initCause(e);
78 throw ex;
79 } catch (IOException e) {
80 InflateException ex = new InflateException(
81 parser.getPositionDescription()
82 + ": " + e.getMessage());
83 ex.initCause(e);
84 throw ex;
85 } finally {
86 parser.close();
87 }
88 }
89
90 /**
91 * Loads a {@link TransitionManager} object from a resource
92 *
93 *
94 *
95 * @param resource The resource id of the transition manager to load
96 * @return The loaded TransitionManager object
97 * @throws android.content.res.Resources.NotFoundException when the
98 * transition manager cannot be loaded
99 */
100 public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) {
101 XmlResourceParser parser = mContext.getResources().getXml(resource);
102 try {
103 return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot);
104 } catch (XmlPullParserException e) {
105 InflateException ex = new InflateException(e.getMessage());
106 ex.initCause(e);
107 throw ex;
108 } catch (IOException e) {
109 InflateException ex = new InflateException(
110 parser.getPositionDescription()
111 + ": " + e.getMessage());
112 ex.initCause(e);
113 throw ex;
114 } finally {
115 parser.close();
116 }
117 }
118
Chet Haasefaebd8f2012-05-18 14:17:57 -0700119 //
120 // Transition loading
121 //
122
123 private Transition createTransitionFromXml(XmlPullParser parser,
Chet Haased82c8ac2013-08-26 14:20:16 -0700124 AttributeSet attrs, TransitionSet transitionSet)
Chet Haasefaebd8f2012-05-18 14:17:57 -0700125 throws XmlPullParserException, IOException {
126
127 Transition transition = null;
128
129 // Make sure we are on a start tag.
130 int type;
131 int depth = parser.getDepth();
132
133 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
134 && type != XmlPullParser.END_DOCUMENT) {
135
136 boolean newTransition = false;
137
138 if (type != XmlPullParser.START_TAG) {
139 continue;
140 }
141
142 String name = parser.getName();
143 if ("fade".equals(name)) {
Chet Haased82c8ac2013-08-26 14:20:16 -0700144 TypedArray a = mContext.obtainStyledAttributes(attrs,
145 com.android.internal.R.styleable.Fade);
146 int fadingMode = a.getInt(com.android.internal.R.styleable.Fade_fadingMode,
147 Fade.IN | Fade.OUT);
148 transition = new Fade(fadingMode);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700149 newTransition = true;
Chet Haased82c8ac2013-08-26 14:20:16 -0700150 } else if ("changeBounds".equals(name)) {
151 transition = new ChangeBounds();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700152 newTransition = true;
153 } else if ("slide".equals(name)) {
George Mountd6107a32014-03-10 16:51:16 -0700154 transition = createSlideTransition(attrs);
155 newTransition = true;
156 } else if ("explode".equals(name)) {
157 transition = new Explode();
158 newTransition = true;
159 } else if ("moveImage".equals(name)) {
160 transition = new MoveImage();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700161 newTransition = true;
George Mount608b87d2014-04-15 09:01:32 -0700162 } else if ("changeTransform".equals(name)) {
163 transition = new ChangeTransform();
164 newTransition = true;
165 } else if ("changeClipBounds".equals(name)) {
166 transition = new ChangeClipBounds();
167 newTransition = true;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700168 } else if ("autoTransition".equals(name)) {
169 transition = new AutoTransition();
170 newTransition = true;
171 } else if ("recolor".equals(name)) {
172 transition = new Recolor();
173 newTransition = true;
Chet Haaseaafc91c2013-10-06 12:10:12 -0700174 } else if ("transitionSet".equals(name)) {
Chet Haased82c8ac2013-08-26 14:20:16 -0700175 transition = new TransitionSet();
176 TypedArray a = mContext.obtainStyledAttributes(attrs,
177 com.android.internal.R.styleable.TransitionSet);
178 int ordering = a.getInt(
179 com.android.internal.R.styleable.TransitionSet_transitionOrdering,
180 TransitionSet.ORDERING_TOGETHER);
181 ((TransitionSet) transition).setOrdering(ordering);
182 createTransitionFromXml(parser, attrs, ((TransitionSet) transition));
183 a.recycle();
Chet Haasefaebd8f2012-05-18 14:17:57 -0700184 newTransition = true;
185 } else if ("targets".equals(name)) {
186 if (parser.getDepth() - 1 > depth && transition != null) {
187 // We're inside the child tag - add targets to the child
Chet Haased82c8ac2013-08-26 14:20:16 -0700188 getTargetIds(parser, attrs, transition);
189 } else if (parser.getDepth() - 1 == depth && transitionSet != null) {
190 // add targets to the set
191 getTargetIds(parser, attrs, transitionSet);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700192 }
193 }
194 if (transition != null || "targets".equals(name)) {
195 if (newTransition) {
196 loadTransition(transition, attrs);
Chet Haased82c8ac2013-08-26 14:20:16 -0700197 if (transitionSet != null) {
198 transitionSet.addTransition(transition);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700199 }
200 }
201 } else {
202 throw new RuntimeException("Unknown scene name: " + parser.getName());
203 }
204 }
205
206 return transition;
207 }
208
George Mountd6107a32014-03-10 16:51:16 -0700209 private Slide createSlideTransition(AttributeSet attrs) {
210 TypedArray a = mContext.obtainStyledAttributes(attrs,
211 com.android.internal.R.styleable.Slide);
George Mountdc21d3b2014-06-05 09:42:48 -0700212 int edge = a.getInt(com.android.internal.R.styleable.Slide_slideEdge, Gravity.BOTTOM);
George Mountd6107a32014-03-10 16:51:16 -0700213 Slide slide = new Slide(edge);
214 a.recycle();
215 return slide;
216 }
217
Chet Haased82c8ac2013-08-26 14:20:16 -0700218 private void getTargetIds(XmlPullParser parser,
Chet Haasefaebd8f2012-05-18 14:17:57 -0700219 AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
220
221 // Make sure we are on a start tag.
222 int type;
223 int depth = parser.getDepth();
224
Chet Haasefaebd8f2012-05-18 14:17:57 -0700225 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
226 && type != XmlPullParser.END_DOCUMENT) {
227
228 if (type != XmlPullParser.START_TAG) {
229 continue;
230 }
231
232 String name = parser.getName();
233 if (name.equals("target")) {
234 TypedArray a = mContext.obtainStyledAttributes(attrs,
Chet Haased82c8ac2013-08-26 14:20:16 -0700235 com.android.internal.R.styleable.TransitionTarget);
236 int id = a.getResourceId(
237 com.android.internal.R.styleable.TransitionTarget_targetId, -1);
George Mount30da61d2014-05-09 13:17:52 -0700238 String viewName;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700239 if (id >= 0) {
George Mountf976c3d2014-04-15 09:01:32 -0700240 transition.addTarget(id);
George Mounta98fb7a2014-04-24 08:57:03 -0700241 } else if ((id = a.getResourceId(
242 com.android.internal.R.styleable.TransitionTarget_excludeId, -1)) >= 0) {
243 transition.excludeTarget(id, true);
George Mount30da61d2014-05-09 13:17:52 -0700244 } else if ((viewName = a.getString(
245 com.android.internal.R.styleable.TransitionTarget_targetViewName))
246 != null) {
247 transition.addTarget(viewName);
248 } else if ((viewName = a.getString(
249 com.android.internal.R.styleable.TransitionTarget_excludeViewName))
250 != null) {
251 transition.excludeTarget(viewName, true);
George Mountf976c3d2014-04-15 09:01:32 -0700252 } else {
George Mounta98fb7a2014-04-24 08:57:03 -0700253 String className = a.getString(
254 com.android.internal.R.styleable.TransitionTarget_excludeClass);
255 try {
George Mountf976c3d2014-04-15 09:01:32 -0700256 if (className != null) {
George Mounta98fb7a2014-04-24 08:57:03 -0700257 Class clazz = Class.forName(className);
258 transition.excludeTarget(clazz, true);
259 } else if ((className = a.getString(
260 com.android.internal.R.styleable.TransitionTarget_targetClass))
261 != null) {
262 Class clazz = Class.forName(className);
263 transition.addTarget(clazz);
George Mountf976c3d2014-04-15 09:01:32 -0700264 }
George Mounta98fb7a2014-04-24 08:57:03 -0700265 } catch (ClassNotFoundException e) {
266 throw new RuntimeException("Could not create " + className, e);
George Mountf976c3d2014-04-15 09:01:32 -0700267 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700268 }
269 } else {
270 throw new RuntimeException("Unknown scene name: " + parser.getName());
271 }
272 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700273 }
274
George Mount7b750622014-05-13 18:08:38 -0700275 private int[] parseMatchOrder(String matchOrderString) {
276 StringTokenizer st = new StringTokenizer(matchOrderString, ",");
277 int matches[] = new int[st.countTokens()];
278 int index = 0;
279 while (st.hasMoreTokens()) {
280 String token = st.nextToken().trim();
281 if (MATCH_ID.equalsIgnoreCase(token)) {
282 matches[index] = Transition.MATCH_ID;
283 } else if (MATCH_INSTANCE.equalsIgnoreCase(token)) {
284 matches[index] = Transition.MATCH_INSTANCE;
285 } else if (MATCH_VIEW_NAME.equalsIgnoreCase(token)) {
286 matches[index] = Transition.MATCH_VIEW_NAME;
287 } else if (MATCH_ITEM_ID.equalsIgnoreCase(token)) {
288 matches[index] = Transition.MATCH_ITEM_ID;
289 } else if (token.isEmpty()) {
290 int[] smallerMatches = new int[matches.length - 1];
291 System.arraycopy(matches, 0, smallerMatches, 0, index);
292 matches = smallerMatches;
293 index--;
294 } else {
295 throw new RuntimeException("Unknown match type in matchOrder: '" + token + "'");
296 }
297 index++;
298 }
299 return matches;
300 }
301
Chet Haasefaebd8f2012-05-18 14:17:57 -0700302 private Transition loadTransition(Transition transition, AttributeSet attrs)
303 throws Resources.NotFoundException {
304
305 TypedArray a =
306 mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition);
307 long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1);
308 if (duration >= 0) {
309 transition.setDuration(duration);
310 }
Chet Haased82c8ac2013-08-26 14:20:16 -0700311 long startDelay = a.getInt(com.android.internal.R.styleable.Transition_startDelay, -1);
312 if (startDelay > 0) {
313 transition.setStartDelay(startDelay);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700314 }
315 final int resID =
316 a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
317 if (resID > 0) {
318 transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID));
319 }
George Mount7b750622014-05-13 18:08:38 -0700320 String matchOrder =
321 a.getString(com.android.internal.R.styleable.Transition_matchOrder);
322 if (matchOrder != null) {
323 transition.setMatchOrder(parseMatchOrder(matchOrder));
324 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700325 a.recycle();
326 return transition;
327 }
328
329 //
330 // TransitionManager loading
331 //
332
333 private TransitionManager createTransitionManagerFromXml(XmlPullParser parser,
334 AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException {
335
336 // Make sure we are on a start tag.
337 int type;
338 int depth = parser.getDepth();
339 TransitionManager transitionManager = null;
340
341 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
342 && type != XmlPullParser.END_DOCUMENT) {
343
344 if (type != XmlPullParser.START_TAG) {
345 continue;
346 }
347
348 String name = parser.getName();
349 if (name.equals("transitionManager")) {
350 transitionManager = new TransitionManager();
351 } else if (name.equals("transition") && (transitionManager != null)) {
352 loadTransition(attrs, sceneRoot, transitionManager);
353 } else {
354 throw new RuntimeException("Unknown scene name: " + parser.getName());
355 }
356 }
357 return transitionManager;
358 }
359
360 private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot,
Chet Haased82c8ac2013-08-26 14:20:16 -0700361 TransitionManager transitionManager) throws Resources.NotFoundException {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700362
363 TypedArray a = mContext.obtainStyledAttributes(attrs,
364 com.android.internal.R.styleable.TransitionManager);
Chet Haased82c8ac2013-08-26 14:20:16 -0700365 int transitionId = a.getResourceId(
Chet Haasefaebd8f2012-05-18 14:17:57 -0700366 com.android.internal.R.styleable.TransitionManager_transition, -1);
Chet Haased82c8ac2013-08-26 14:20:16 -0700367 int fromId = a.getResourceId(
Chet Haasefaebd8f2012-05-18 14:17:57 -0700368 com.android.internal.R.styleable.TransitionManager_fromScene, -1);
George Mounte1803372014-02-26 19:00:52 +0000369 Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext);
Chet Haased82c8ac2013-08-26 14:20:16 -0700370 int toId = a.getResourceId(
Chet Haasefaebd8f2012-05-18 14:17:57 -0700371 com.android.internal.R.styleable.TransitionManager_toScene, -1);
George Mounte1803372014-02-26 19:00:52 +0000372 Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext);
373
Chet Haasefaebd8f2012-05-18 14:17:57 -0700374 if (transitionId >= 0) {
375 Transition transition = inflateTransition(transitionId);
376 if (transition != null) {
George Mount31a21722014-03-24 17:44:36 -0700377 if (toScene == null) {
378 throw new RuntimeException("No toScene for transition ID " + transitionId);
379 }
George Mounte1803372014-02-26 19:00:52 +0000380 if (fromScene == null) {
George Mount31a21722014-03-24 17:44:36 -0700381 transitionManager.setTransition(toScene, transition);
George Mounte1803372014-02-26 19:00:52 +0000382 } else {
383 transitionManager.setTransition(fromScene, toScene, transition);
Adam Powellcfbe9be2013-11-06 14:58:58 -0800384 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700385 }
386 }
387 a.recycle();
388 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700389}