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