blob: 479e7572b14e5deffce76303f5c83c68ecac6a60 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 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.view;
18
Gilles Debunne30301932010-06-16 18:32:00 -070019import org.xmlpull.v1.XmlPullParser;
20import org.xmlpull.v1.XmlPullParserException;
21
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.Context;
23import android.content.res.TypedArray;
24import android.content.res.XmlResourceParser;
25import android.util.AttributeSet;
26import android.util.Xml;
27
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import java.io.IOException;
29import java.lang.reflect.Constructor;
30import java.util.HashMap;
31
32/**
33 * This class is used to instantiate layout XML file into its corresponding View
34 * objects. It is never be used directly -- use
35 * {@link android.app.Activity#getLayoutInflater()} or
36 * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
37 * that is already hooked up to the current context and correctly configured
38 * for the device you are running on. For example:
39 *
40 * <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService
41 * Context.LAYOUT_INFLATER_SERVICE);</pre>
42 *
43 * <p>
44 * To create a new LayoutInflater with an additional {@link Factory} for your
45 * own views, you can use {@link #cloneInContext} to clone an existing
46 * ViewFactory, and then call {@link #setFactory} on it to include your
47 * Factory.
48 *
49 * <p>
50 * For performance reasons, view inflation relies heavily on pre-processing of
51 * XML files that is done at build time. Therefore, it is not currently possible
52 * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
53 * it only works with an XmlPullParser returned from a compiled resource
54 * (R.<em>something</em> file.)
55 *
56 * @see Context#getSystemService
57 */
58public abstract class LayoutInflater {
59 private final boolean DEBUG = false;
60
61 /**
62 * This field should be made private, so it is hidden from the SDK.
63 * {@hide}
64 */
65 protected final Context mContext;
66
67 // these are optional, set by the caller
68 private boolean mFactorySet;
69 private Factory mFactory;
70 private Filter mFilter;
71
72 private final Object[] mConstructorArgs = new Object[2];
73
Gilles Debunne30301932010-06-16 18:32:00 -070074 private static final Class<?>[] mConstructorSignature = new Class[] {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 Context.class, AttributeSet.class};
76
Gilles Debunne30301932010-06-16 18:32:00 -070077 private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
78 new HashMap<String, Constructor<? extends View>>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079
80 private HashMap<String, Boolean> mFilterMap;
81
82 private static final String TAG_MERGE = "merge";
83 private static final String TAG_INCLUDE = "include";
84 private static final String TAG_REQUEST_FOCUS = "requestFocus";
85
86 /**
87 * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
88 * to be inflated.
89 *
90 */
91 public interface Filter {
92 /**
93 * Hook to allow clients of the LayoutInflater to restrict the set of Views
94 * that are allowed to be inflated.
95 *
96 * @param clazz The class object for the View that is about to be inflated
97 *
98 * @return True if this class is allowed to be inflated, or false otherwise
99 */
Gilles Debunnee6ac8b92010-06-17 10:55:04 -0700100 @SuppressWarnings("unchecked")
101 boolean onLoadClass(Class clazz);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 }
103
104 public interface Factory {
105 /**
106 * Hook you can supply that is called when inflating from a LayoutInflater.
107 * You can use this to customize the tag names available in your XML
108 * layout files.
109 *
110 * <p>
111 * Note that it is good practice to prefix these custom names with your
112 * package (i.e., com.coolcompany.apps) to avoid conflicts with system
113 * names.
114 *
115 * @param name Tag name to be inflated.
116 * @param context The context the view is being created in.
117 * @param attrs Inflation attributes as specified in XML file.
118 *
119 * @return View Newly created view. Return null for the default
120 * behavior.
121 */
122 public View onCreateView(String name, Context context, AttributeSet attrs);
123 }
124
125 private static class FactoryMerger implements Factory {
126 private final Factory mF1, mF2;
127
128 FactoryMerger(Factory f1, Factory f2) {
129 mF1 = f1;
130 mF2 = f2;
131 }
132
133 public View onCreateView(String name, Context context, AttributeSet attrs) {
134 View v = mF1.onCreateView(name, context, attrs);
135 if (v != null) return v;
136 return mF2.onCreateView(name, context, attrs);
137 }
138 }
139
140 /**
141 * Create a new LayoutInflater instance associated with a particular Context.
142 * Applications will almost always want to use
143 * {@link Context#getSystemService Context.getSystemService()} to retrieve
144 * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
145 *
146 * @param context The Context in which this LayoutInflater will create its
147 * Views; most importantly, this supplies the theme from which the default
148 * values for their attributes are retrieved.
149 */
150 protected LayoutInflater(Context context) {
151 mContext = context;
152 }
153
154 /**
155 * Create a new LayoutInflater instance that is a copy of an existing
156 * LayoutInflater, optionally with its Context changed. For use in
157 * implementing {@link #cloneInContext}.
158 *
159 * @param original The original LayoutInflater to copy.
160 * @param newContext The new Context to use.
161 */
162 protected LayoutInflater(LayoutInflater original, Context newContext) {
163 mContext = newContext;
164 mFactory = original.mFactory;
165 mFilter = original.mFilter;
166 }
167
168 /**
169 * Obtains the LayoutInflater from the given context.
170 */
171 public static LayoutInflater from(Context context) {
172 LayoutInflater LayoutInflater =
173 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
174 if (LayoutInflater == null) {
175 throw new AssertionError("LayoutInflater not found.");
176 }
177 return LayoutInflater;
178 }
179
180 /**
181 * Create a copy of the existing LayoutInflater object, with the copy
182 * pointing to a different Context than the original. This is used by
183 * {@link ContextThemeWrapper} to create a new LayoutInflater to go along
184 * with the new Context theme.
185 *
186 * @param newContext The new Context to associate with the new LayoutInflater.
187 * May be the same as the original Context if desired.
188 *
189 * @return Returns a brand spanking new LayoutInflater object associated with
190 * the given Context.
191 */
192 public abstract LayoutInflater cloneInContext(Context newContext);
193
194 /**
195 * Return the context we are running in, for access to resources, class
196 * loader, etc.
197 */
198 public Context getContext() {
199 return mContext;
200 }
201
202 /**
203 * Return the current factory (or null). This is called on each element
204 * name. If the factory returns a View, add that to the hierarchy. If it
205 * returns null, proceed to call onCreateView(name).
206 */
207 public final Factory getFactory() {
208 return mFactory;
209 }
210
211 /**
212 * Attach a custom Factory interface for creating views while using
213 * this LayoutInflater. This must not be null, and can only be set once;
214 * after setting, you can not change the factory. This is
215 * called on each element name as the xml is parsed. If the factory returns
216 * a View, that is added to the hierarchy. If it returns null, the next
217 * factory default {@link #onCreateView} method is called.
218 *
219 * <p>If you have an existing
220 * LayoutInflater and want to add your own factory to it, use
221 * {@link #cloneInContext} to clone the existing instance and then you
222 * can use this function (once) on the returned new instance. This will
223 * merge your own factory with whatever factory the original instance is
224 * using.
225 */
226 public void setFactory(Factory factory) {
227 if (mFactorySet) {
228 throw new IllegalStateException("A factory has already been set on this LayoutInflater");
229 }
230 if (factory == null) {
231 throw new NullPointerException("Given factory can not be null");
232 }
233 mFactorySet = true;
234 if (mFactory == null) {
235 mFactory = factory;
236 } else {
237 mFactory = new FactoryMerger(factory, mFactory);
238 }
239 }
240
241 /**
242 * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views
243 * that are allowed to be inflated.
244 */
245 public Filter getFilter() {
246 return mFilter;
247 }
248
249 /**
250 * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
251 * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
252 * throw an {@link InflateException}. This filter will replace any previous filter set on this
253 * LayoutInflater.
254 *
255 * @param filter The Filter which restricts the set of Views that are allowed to be inflated.
256 * This filter will replace any previous filter set on this LayoutInflater.
257 */
258 public void setFilter(Filter filter) {
259 mFilter = filter;
260 if (filter != null) {
261 mFilterMap = new HashMap<String, Boolean>();
262 }
263 }
264
265 /**
266 * Inflate a new view hierarchy from the specified xml resource. Throws
267 * {@link InflateException} if there is an error.
268 *
269 * @param resource ID for an XML layout resource to load (e.g.,
270 * <code>R.layout.main_page</code>)
271 * @param root Optional view to be the parent of the generated hierarchy.
272 * @return The root View of the inflated hierarchy. If root was supplied,
273 * this is the root View; otherwise it is the root of the inflated
274 * XML file.
275 */
276 public View inflate(int resource, ViewGroup root) {
277 return inflate(resource, root, root != null);
278 }
279
280 /**
281 * Inflate a new view hierarchy from the specified xml node. Throws
282 * {@link InflateException} if there is an error. *
283 * <p>
284 * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
285 * reasons, view inflation relies heavily on pre-processing of XML files
286 * that is done at build time. Therefore, it is not currently possible to
287 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
288 *
289 * @param parser XML dom node containing the description of the view
290 * hierarchy.
291 * @param root Optional view to be the parent of the generated hierarchy.
292 * @return The root View of the inflated hierarchy. If root was supplied,
293 * this is the root View; otherwise it is the root of the inflated
294 * XML file.
295 */
296 public View inflate(XmlPullParser parser, ViewGroup root) {
297 return inflate(parser, root, root != null);
298 }
299
300 /**
301 * Inflate a new view hierarchy from the specified xml resource. Throws
302 * {@link InflateException} if there is an error.
303 *
304 * @param resource ID for an XML layout resource to load (e.g.,
305 * <code>R.layout.main_page</code>)
306 * @param root Optional view to be the parent of the generated hierarchy (if
307 * <em>attachToRoot</em> is true), or else simply an object that
308 * provides a set of LayoutParams values for root of the returned
309 * hierarchy (if <em>attachToRoot</em> is false.)
310 * @param attachToRoot Whether the inflated hierarchy should be attached to
311 * the root parameter? If false, root is only used to create the
312 * correct subclass of LayoutParams for the root view in the XML.
313 * @return The root View of the inflated hierarchy. If root was supplied and
314 * attachToRoot is true, this is root; otherwise it is the root of
315 * the inflated XML file.
316 */
317 public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
318 if (DEBUG) System.out.println("INFLATING from resource: " + resource);
319 XmlResourceParser parser = getContext().getResources().getLayout(resource);
320 try {
321 return inflate(parser, root, attachToRoot);
322 } finally {
323 parser.close();
324 }
325 }
326
327 /**
328 * Inflate a new view hierarchy from the specified XML node. Throws
329 * {@link InflateException} if there is an error.
330 * <p>
331 * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
332 * reasons, view inflation relies heavily on pre-processing of XML files
333 * that is done at build time. Therefore, it is not currently possible to
334 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
335 *
336 * @param parser XML dom node containing the description of the view
337 * hierarchy.
338 * @param root Optional view to be the parent of the generated hierarchy (if
339 * <em>attachToRoot</em> is true), or else simply an object that
340 * provides a set of LayoutParams values for root of the returned
341 * hierarchy (if <em>attachToRoot</em> is false.)
342 * @param attachToRoot Whether the inflated hierarchy should be attached to
343 * the root parameter? If false, root is only used to create the
344 * correct subclass of LayoutParams for the root view in the XML.
345 * @return The root View of the inflated hierarchy. If root was supplied and
346 * attachToRoot is true, this is root; otherwise it is the root of
347 * the inflated XML file.
348 */
349 public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
350 synchronized (mConstructorArgs) {
351 final AttributeSet attrs = Xml.asAttributeSet(parser);
352 mConstructorArgs[0] = mContext;
353 View result = root;
354
355 try {
356 // Look for the root node.
357 int type;
358 while ((type = parser.next()) != XmlPullParser.START_TAG &&
359 type != XmlPullParser.END_DOCUMENT) {
360 // Empty
361 }
362
363 if (type != XmlPullParser.START_TAG) {
364 throw new InflateException(parser.getPositionDescription()
365 + ": No start tag found!");
366 }
367
368 final String name = parser.getName();
369
370 if (DEBUG) {
371 System.out.println("**************************");
372 System.out.println("Creating root view: "
373 + name);
374 System.out.println("**************************");
375 }
376
377 if (TAG_MERGE.equals(name)) {
378 if (root == null || !attachToRoot) {
379 throw new InflateException("<merge /> can be used only with a valid "
380 + "ViewGroup root and attachToRoot=true");
381 }
382
Romain Guy9295ada2010-06-15 11:33:24 -0700383 rInflate(parser, root, attrs, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 } else {
385 // Temp is the root view that was found in the xml
386 View temp = createViewFromTag(name, attrs);
387
388 ViewGroup.LayoutParams params = null;
389
390 if (root != null) {
391 if (DEBUG) {
392 System.out.println("Creating params from root: " +
393 root);
394 }
395 // Create layout params that match root, if supplied
396 params = root.generateLayoutParams(attrs);
397 if (!attachToRoot) {
398 // Set the layout params for temp if we are not
399 // attaching. (If we are, we use addView, below)
400 temp.setLayoutParams(params);
401 }
402 }
403
404 if (DEBUG) {
405 System.out.println("-----> start inflating children");
406 }
407 // Inflate all children under temp
Romain Guy9295ada2010-06-15 11:33:24 -0700408 rInflate(parser, temp, attrs, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 if (DEBUG) {
410 System.out.println("-----> done inflating children");
411 }
412
413 // We are supposed to attach all the views we found (int temp)
414 // to root. Do that now.
415 if (root != null && attachToRoot) {
416 root.addView(temp, params);
417 }
418
419 // Decide whether to return the root that was passed in or the
420 // top view found in xml.
421 if (root == null || !attachToRoot) {
422 result = temp;
423 }
424 }
425
426 } catch (XmlPullParserException e) {
427 InflateException ex = new InflateException(e.getMessage());
428 ex.initCause(e);
429 throw ex;
430 } catch (IOException e) {
431 InflateException ex = new InflateException(
432 parser.getPositionDescription()
433 + ": " + e.getMessage());
434 ex.initCause(e);
435 throw ex;
436 }
437
438 return result;
439 }
440 }
441
442 /**
443 * Low-level function for instantiating a view by name. This attempts to
444 * instantiate a view class of the given <var>name</var> found in this
445 * LayoutInflater's ClassLoader.
446 *
447 * <p>
448 * There are two things that can happen in an error case: either the
449 * exception describing the error will be thrown, or a null will be
450 * returned. You must deal with both possibilities -- the former will happen
451 * the first time createView() is called for a class of a particular name,
452 * the latter every time there-after for that class name.
453 *
454 * @param name The full name of the class to be instantiated.
455 * @param attrs The XML attributes supplied for this instance.
456 *
Gilles Debunne30301932010-06-16 18:32:00 -0700457 * @return View The newly instantiated view, or null.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 */
459 public final View createView(String name, String prefix, AttributeSet attrs)
460 throws ClassNotFoundException, InflateException {
Gilles Debunne30301932010-06-16 18:32:00 -0700461 Constructor<? extends View> constructor = sConstructorMap.get(name);
462 Class<? extends View> clazz = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463
464 try {
465 if (constructor == null) {
466 // Class not found in the cache, see if it's real, and try to add it
Romain Guyd03b8802009-09-16 14:36:16 -0700467 clazz = mContext.getClassLoader().loadClass(
Gilles Debunne30301932010-06-16 18:32:00 -0700468 prefix != null ? (prefix + name) : name).asSubclass(View.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469
470 if (mFilter != null && clazz != null) {
471 boolean allowed = mFilter.onLoadClass(clazz);
472 if (!allowed) {
473 failNotAllowed(name, prefix, attrs);
474 }
475 }
476 constructor = clazz.getConstructor(mConstructorSignature);
477 sConstructorMap.put(name, constructor);
478 } else {
479 // If we have a filter, apply it to cached constructor
480 if (mFilter != null) {
481 // Have we seen this name before?
482 Boolean allowedState = mFilterMap.get(name);
483 if (allowedState == null) {
484 // New class -- remember whether it is allowed
Romain Guyd03b8802009-09-16 14:36:16 -0700485 clazz = mContext.getClassLoader().loadClass(
Gilles Debunne30301932010-06-16 18:32:00 -0700486 prefix != null ? (prefix + name) : name).asSubclass(View.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800487
488 boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
489 mFilterMap.put(name, allowed);
490 if (!allowed) {
491 failNotAllowed(name, prefix, attrs);
492 }
493 } else if (allowedState.equals(Boolean.FALSE)) {
494 failNotAllowed(name, prefix, attrs);
495 }
496 }
497 }
498
499 Object[] args = mConstructorArgs;
500 args[1] = attrs;
Gilles Debunne30301932010-06-16 18:32:00 -0700501 return constructor.newInstance(args);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800502
503 } catch (NoSuchMethodException e) {
504 InflateException ie = new InflateException(attrs.getPositionDescription()
505 + ": Error inflating class "
506 + (prefix != null ? (prefix + name) : name));
507 ie.initCause(e);
508 throw ie;
509
Gilles Debunne30301932010-06-16 18:32:00 -0700510 } catch (ClassCastException e) {
511 // If loaded class is not a View subclass
512 InflateException ie = new InflateException(attrs.getPositionDescription()
513 + ": Class is not a View "
514 + (prefix != null ? (prefix + name) : name));
515 ie.initCause(e);
516 throw ie;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800517 } catch (ClassNotFoundException e) {
518 // If loadClass fails, we should propagate the exception.
519 throw e;
520 } catch (Exception e) {
521 InflateException ie = new InflateException(attrs.getPositionDescription()
522 + ": Error inflating class "
Romain Guyd03b8802009-09-16 14:36:16 -0700523 + (clazz == null ? "<unknown>" : clazz.getName()));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 ie.initCause(e);
525 throw ie;
526 }
527 }
528
529 /**
Gilles Debunne30301932010-06-16 18:32:00 -0700530 * Throw an exception because the specified class is not allowed to be inflated.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531 */
532 private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
533 InflateException ie = new InflateException(attrs.getPositionDescription()
534 + ": Class not allowed to be inflated "
535 + (prefix != null ? (prefix + name) : name));
536 throw ie;
537 }
538
539 /**
540 * This routine is responsible for creating the correct subclass of View
541 * given the xml element name. Override it to handle custom view objects. If
542 * you override this in your subclass be sure to call through to
543 * super.onCreateView(name) for names you do not recognize.
544 *
545 * @param name The fully qualified class name of the View to be create.
546 * @param attrs An AttributeSet of attributes to apply to the View.
547 *
548 * @return View The View created.
549 */
550 protected View onCreateView(String name, AttributeSet attrs)
551 throws ClassNotFoundException {
552 return createView(name, "android.view.", attrs);
553 }
554
555 /*
556 * default visibility so the BridgeInflater can override it.
557 */
558 View createViewFromTag(String name, AttributeSet attrs) {
559 if (name.equals("view")) {
560 name = attrs.getAttributeValue(null, "class");
561 }
562
563 if (DEBUG) System.out.println("******** Creating view: " + name);
564
565 try {
566 View view = (mFactory == null) ? null : mFactory.onCreateView(name,
567 mContext, attrs);
568
569 if (view == null) {
570 if (-1 == name.indexOf('.')) {
571 view = onCreateView(name, attrs);
572 } else {
573 view = createView(name, null, attrs);
574 }
575 }
576
577 if (DEBUG) System.out.println("Created view is: " + view);
578 return view;
579
580 } catch (InflateException e) {
581 throw e;
582
583 } catch (ClassNotFoundException e) {
584 InflateException ie = new InflateException(attrs.getPositionDescription()
585 + ": Error inflating class " + name);
586 ie.initCause(e);
587 throw ie;
588
589 } catch (Exception e) {
590 InflateException ie = new InflateException(attrs.getPositionDescription()
591 + ": Error inflating class " + name);
592 ie.initCause(e);
593 throw ie;
594 }
595 }
596
597 /**
598 * Recursive method used to descend down the xml hierarchy and instantiate
599 * views, instantiate their children, and then call onFinishInflate().
600 */
Romain Guy9295ada2010-06-15 11:33:24 -0700601 private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
602 boolean finishInflate) throws XmlPullParserException, IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603
604 final int depth = parser.getDepth();
605 int type;
606
607 while (((type = parser.next()) != XmlPullParser.END_TAG ||
608 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
609
610 if (type != XmlPullParser.START_TAG) {
611 continue;
612 }
613
614 final String name = parser.getName();
615
616 if (TAG_REQUEST_FOCUS.equals(name)) {
617 parseRequestFocus(parser, parent);
618 } else if (TAG_INCLUDE.equals(name)) {
619 if (parser.getDepth() == 0) {
620 throw new InflateException("<include /> cannot be the root element");
621 }
622 parseInclude(parser, parent, attrs);
623 } else if (TAG_MERGE.equals(name)) {
624 throw new InflateException("<merge /> must be the root element");
625 } else {
626 final View view = createViewFromTag(name, attrs);
627 final ViewGroup viewGroup = (ViewGroup) parent;
628 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
Romain Guy9295ada2010-06-15 11:33:24 -0700629 rInflate(parser, view, attrs, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630 viewGroup.addView(view, params);
631 }
632 }
633
Romain Guy9295ada2010-06-15 11:33:24 -0700634 if (finishInflate) parent.onFinishInflate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 }
636
637 private void parseRequestFocus(XmlPullParser parser, View parent)
638 throws XmlPullParserException, IOException {
639 int type;
640 parent.requestFocus();
641 final int currentDepth = parser.getDepth();
642 while (((type = parser.next()) != XmlPullParser.END_TAG ||
643 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
644 // Empty
645 }
646 }
647
648 private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs)
649 throws XmlPullParserException, IOException {
650
651 int type;
652
653 if (parent instanceof ViewGroup) {
654 final int layout = attrs.getAttributeResourceValue(null, "layout", 0);
655 if (layout == 0) {
656 final String value = attrs.getAttributeValue(null, "layout");
657 if (value == null) {
658 throw new InflateException("You must specifiy a layout in the"
659 + " include tag: <include layout=\"@layout/layoutID\" />");
660 } else {
661 throw new InflateException("You must specifiy a valid layout "
662 + "reference. The layout ID " + value + " is not valid.");
663 }
664 } else {
665 final XmlResourceParser childParser =
666 getContext().getResources().getLayout(layout);
667
668 try {
669 final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
670
671 while ((type = childParser.next()) != XmlPullParser.START_TAG &&
672 type != XmlPullParser.END_DOCUMENT) {
673 // Empty.
674 }
675
676 if (type != XmlPullParser.START_TAG) {
677 throw new InflateException(childParser.getPositionDescription() +
678 ": No start tag found!");
679 }
680
681 final String childName = childParser.getName();
682
683 if (TAG_MERGE.equals(childName)) {
684 // Inflate all children.
Romain Guy9295ada2010-06-15 11:33:24 -0700685 rInflate(childParser, parent, childAttrs, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800686 } else {
687 final View view = createViewFromTag(childName, childAttrs);
688 final ViewGroup group = (ViewGroup) parent;
689
690 // We try to load the layout params set in the <include /> tag. If
691 // they don't exist, we will rely on the layout params set in the
692 // included XML file.
693 // During a layoutparams generation, a runtime exception is thrown
694 // if either layout_width or layout_height is missing. We catch
695 // this exception and set localParams accordingly: true means we
696 // successfully loaded layout params from the <include /> tag,
697 // false means we need to rely on the included layout params.
698 ViewGroup.LayoutParams params = null;
699 try {
700 params = group.generateLayoutParams(attrs);
701 } catch (RuntimeException e) {
702 params = group.generateLayoutParams(childAttrs);
703 } finally {
704 if (params != null) {
705 view.setLayoutParams(params);
706 }
707 }
708
709 // Inflate all children.
Romain Guy9295ada2010-06-15 11:33:24 -0700710 rInflate(childParser, view, childAttrs, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800711
712 // Attempt to override the included layout's android:id with the
713 // one set on the <include /> tag itself.
714 TypedArray a = mContext.obtainStyledAttributes(attrs,
715 com.android.internal.R.styleable.View, 0, 0);
716 int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
717 // While we're at it, let's try to override android:visibility.
718 int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
719 a.recycle();
720
721 if (id != View.NO_ID) {
722 view.setId(id);
723 }
724
725 switch (visibility) {
726 case 0:
727 view.setVisibility(View.VISIBLE);
728 break;
729 case 1:
730 view.setVisibility(View.INVISIBLE);
731 break;
732 case 2:
733 view.setVisibility(View.GONE);
734 break;
735 }
736
737 group.addView(view);
738 }
739 } finally {
740 childParser.close();
741 }
742 }
743 } else {
744 throw new InflateException("<include /> can only be used inside of a ViewGroup");
745 }
746
747 final int currentDepth = parser.getDepth();
748 while (((type = parser.next()) != XmlPullParser.END_TAG ||
749 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
750 // Empty
751 }
752 }
753}