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 17package android.view; 18 19import android.graphics.Canvas; 20import android.os.Handler; 21import android.os.Message; 22import android.widget.FrameLayout; 23import org.xmlpull.v1.XmlPullParser; 24import org.xmlpull.v1.XmlPullParserException; 25 26import android.content.Context; 27import android.content.res.TypedArray; 28import android.content.res.XmlResourceParser; 29import android.util.AttributeSet; 30import android.util.Xml; 31 32import java.io.IOException; 33import java.lang.reflect.Constructor; 34import java.util.HashMap; 35 36/** 37 * Instantiates a layout XML file into its corresponding {@link android.view.View} 38 * objects. It is never used directly. Instead, use 39 * {@link android.app.Activity#getLayoutInflater()} or 40 * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance 41 * that is already hooked up to the current context and correctly configured 42 * for the device you are running on. For example: 43 * 44 * <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService 45 * (Context.LAYOUT_INFLATER_SERVICE);</pre> 46 * 47 * <p> 48 * To create a new LayoutInflater with an additional {@link Factory} for your 49 * own views, you can use {@link #cloneInContext} to clone an existing 50 * ViewFactory, and then call {@link #setFactory} on it to include your 51 * Factory. 52 * 53 * <p> 54 * For performance reasons, view inflation relies heavily on pre-processing of 55 * XML files that is done at build time. Therefore, it is not currently possible 56 * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime; 57 * it only works with an XmlPullParser returned from a compiled resource 58 * (R.<em>something</em> file.) 59 * 60 * @see Context#getSystemService 61 */ 62public abstract class LayoutInflater { 63 private final boolean DEBUG = false; 64 65 /** 66 * This field should be made private, so it is hidden from the SDK. 67 * {@hide} 68 */ 69 protected final Context mContext; 70 71 // these are optional, set by the caller 72 private boolean mFactorySet; 73 private Factory mFactory; 74 private Factory2 mFactory2; 75 private Factory2 mPrivateFactory; 76 private Filter mFilter; 77 78 final Object[] mConstructorArgs = new Object[2]; 79 80 static final Class<?>[] mConstructorSignature = new Class[] { 81 Context.class, AttributeSet.class}; 82 83 private static final HashMap<String, Constructor<? extends View>> sConstructorMap = 84 new HashMap<String, Constructor<? extends View>>(); 85 86 private HashMap<String, Boolean> mFilterMap; 87 88 private static final String TAG_MERGE = "merge"; 89 private static final String TAG_INCLUDE = "include"; 90 private static final String TAG_1995 = "blink"; 91 private static final String TAG_REQUEST_FOCUS = "requestFocus"; 92 93 /** 94 * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed 95 * to be inflated. 96 * 97 */ 98 public interface Filter { 99 /** 100 * Hook to allow clients of the LayoutInflater to restrict the set of Views 101 * that are allowed to be inflated. 102 * 103 * @param clazz The class object for the View that is about to be inflated 104 * 105 * @return True if this class is allowed to be inflated, or false otherwise 106 */ 107 @SuppressWarnings("unchecked") 108 boolean onLoadClass(Class clazz); 109 } 110 111 public interface Factory { 112 /** 113 * Hook you can supply that is called when inflating from a LayoutInflater. 114 * You can use this to customize the tag names available in your XML 115 * layout files. 116 * 117 * <p> 118 * Note that it is good practice to prefix these custom names with your 119 * package (i.e., com.coolcompany.apps) to avoid conflicts with system 120 * names. 121 * 122 * @param name Tag name to be inflated. 123 * @param context The context the view is being created in. 124 * @param attrs Inflation attributes as specified in XML file. 125 * 126 * @return View Newly created view. Return null for the default 127 * behavior. 128 */ 129 public View onCreateView(String name, Context context, AttributeSet attrs); 130 } 131 132 public interface Factory2 extends Factory { 133 /** 134 * Version of {@link #onCreateView(String, Context, AttributeSet)} 135 * that also supplies the parent that the view created view will be 136 * placed in. 137 * 138 * @param parent The parent that the created view will be placed 139 * in; <em>note that this may be null</em>. 140 * @param name Tag name to be inflated. 141 * @param context The context the view is being created in. 142 * @param attrs Inflation attributes as specified in XML file. 143 * 144 * @return View Newly created view. Return null for the default 145 * behavior. 146 */ 147 public View onCreateView(View parent, String name, Context context, AttributeSet attrs); 148 } 149 150 private static class FactoryMerger implements Factory2 { 151 private final Factory mF1, mF2; 152 private final Factory2 mF12, mF22; 153 154 FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) { 155 mF1 = f1; 156 mF2 = f2; 157 mF12 = f12; 158 mF22 = f22; 159 } 160 161 public View onCreateView(String name, Context context, AttributeSet attrs) { 162 View v = mF1.onCreateView(name, context, attrs); 163 if (v != null) return v; 164 return mF2.onCreateView(name, context, attrs); 165 } 166 167 public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { 168 View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs) 169 : mF1.onCreateView(name, context, attrs); 170 if (v != null) return v; 171 return mF22 != null ? mF22.onCreateView(parent, name, context, attrs) 172 : mF2.onCreateView(name, context, attrs); 173 } 174 } 175 176 /** 177 * Create a new LayoutInflater instance associated with a particular Context. 178 * Applications will almost always want to use 179 * {@link Context#getSystemService Context.getSystemService()} to retrieve 180 * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}. 181 * 182 * @param context The Context in which this LayoutInflater will create its 183 * Views; most importantly, this supplies the theme from which the default 184 * values for their attributes are retrieved. 185 */ 186 protected LayoutInflater(Context context) { 187 mContext = context; 188 } 189 190 /** 191 * Create a new LayoutInflater instance that is a copy of an existing 192 * LayoutInflater, optionally with its Context changed. For use in 193 * implementing {@link #cloneInContext}. 194 * 195 * @param original The original LayoutInflater to copy. 196 * @param newContext The new Context to use. 197 */ 198 protected LayoutInflater(LayoutInflater original, Context newContext) { 199 mContext = newContext; 200 mFactory = original.mFactory; 201 mFactory2 = original.mFactory2; 202 mPrivateFactory = original.mPrivateFactory; 203 mFilter = original.mFilter; 204 } 205 206 /** 207 * Obtains the LayoutInflater from the given context. 208 */ 209 public static LayoutInflater from(Context context) { 210 LayoutInflater LayoutInflater = 211 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 212 if (LayoutInflater == null) { 213 throw new AssertionError("LayoutInflater not found."); 214 } 215 return LayoutInflater; 216 } 217 218 /** 219 * Create a copy of the existing LayoutInflater object, with the copy 220 * pointing to a different Context than the original. This is used by 221 * {@link ContextThemeWrapper} to create a new LayoutInflater to go along 222 * with the new Context theme. 223 * 224 * @param newContext The new Context to associate with the new LayoutInflater. 225 * May be the same as the original Context if desired. 226 * 227 * @return Returns a brand spanking new LayoutInflater object associated with 228 * the given Context. 229 */ 230 public abstract LayoutInflater cloneInContext(Context newContext); 231 232 /** 233 * Return the context we are running in, for access to resources, class 234 * loader, etc. 235 */ 236 public Context getContext() { 237 return mContext; 238 } 239 240 /** 241 * Return the current {@link Factory} (or null). This is called on each element 242 * name. If the factory returns a View, add that to the hierarchy. If it 243 * returns null, proceed to call onCreateView(name). 244 */ 245 public final Factory getFactory() { 246 return mFactory; 247 } 248 249 /** 250 * Return the current {@link Factory2}. Returns null if no factory is set 251 * or the set factory does not implement the {@link Factory2} interface. 252 * This is called on each element 253 * name. If the factory returns a View, add that to the hierarchy. If it 254 * returns null, proceed to call onCreateView(name). 255 */ 256 public final Factory2 getFactory2() { 257 return mFactory2; 258 } 259 260 /** 261 * Attach a custom Factory interface for creating views while using 262 * this LayoutInflater. This must not be null, and can only be set once; 263 * after setting, you can not change the factory. This is 264 * called on each element name as the xml is parsed. If the factory returns 265 * a View, that is added to the hierarchy. If it returns null, the next 266 * factory default {@link #onCreateView} method is called. 267 * 268 * <p>If you have an existing 269 * LayoutInflater and want to add your own factory to it, use 270 * {@link #cloneInContext} to clone the existing instance and then you 271 * can use this function (once) on the returned new instance. This will 272 * merge your own factory with whatever factory the original instance is 273 * using. 274 */ 275 public void setFactory(Factory factory) { 276 if (mFactorySet) { 277 throw new IllegalStateException("A factory has already been set on this LayoutInflater"); 278 } 279 if (factory == null) { 280 throw new NullPointerException("Given factory can not be null"); 281 } 282 mFactorySet = true; 283 if (mFactory == null) { 284 mFactory = factory; 285 } else { 286 mFactory = new FactoryMerger(factory, null, mFactory, mFactory2); 287 } 288 } 289 290 /** 291 * Like {@link #setFactory}, but allows you to set a {@link Factory2} 292 * interface. 293 */ 294 public void setFactory2(Factory2 factory) { 295 if (mFactorySet) { 296 throw new IllegalStateException("A factory has already been set on this LayoutInflater"); 297 } 298 if (factory == null) { 299 throw new NullPointerException("Given factory can not be null"); 300 } 301 mFactorySet = true; 302 if (mFactory == null) { 303 mFactory = mFactory2 = factory; 304 } else { 305 mFactory = new FactoryMerger(factory, factory, mFactory, mFactory2); 306 } 307 } 308 309 /** 310 * @hide for use by framework 311 */ 312 public void setPrivateFactory(Factory2 factory) { 313 mPrivateFactory = factory; 314 } 315 316 /** 317 * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views 318 * that are allowed to be inflated. 319 */ 320 public Filter getFilter() { 321 return mFilter; 322 } 323 324 /** 325 * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated 326 * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will 327 * throw an {@link InflateException}. This filter will replace any previous filter set on this 328 * LayoutInflater. 329 * 330 * @param filter The Filter which restricts the set of Views that are allowed to be inflated. 331 * This filter will replace any previous filter set on this LayoutInflater. 332 */ 333 public void setFilter(Filter filter) { 334 mFilter = filter; 335 if (filter != null) { 336 mFilterMap = new HashMap<String, Boolean>(); 337 } 338 } 339 340 /** 341 * Inflate a new view hierarchy from the specified xml resource. Throws 342 * {@link InflateException} if there is an error. 343 * 344 * @param resource ID for an XML layout resource to load (e.g., 345 * <code>R.layout.main_page</code>) 346 * @param root Optional view to be the parent of the generated hierarchy. 347 * @return The root View of the inflated hierarchy. If root was supplied, 348 * this is the root View; otherwise it is the root of the inflated 349 * XML file. 350 */ 351 public View inflate(int resource, ViewGroup root) { 352 return inflate(resource, root, root != null); 353 } 354 355 /** 356 * Inflate a new view hierarchy from the specified xml node. Throws 357 * {@link InflateException} if there is an error. * 358 * <p> 359 * <em><strong>Important</strong></em> For performance 360 * reasons, view inflation relies heavily on pre-processing of XML files 361 * that is done at build time. Therefore, it is not currently possible to 362 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. 363 * 364 * @param parser XML dom node containing the description of the view 365 * hierarchy. 366 * @param root Optional view to be the parent of the generated hierarchy. 367 * @return The root View of the inflated hierarchy. If root was supplied, 368 * this is the root View; otherwise it is the root of the inflated 369 * XML file. 370 */ 371 public View inflate(XmlPullParser parser, ViewGroup root) { 372 return inflate(parser, root, root != null); 373 } 374 375 /** 376 * Inflate a new view hierarchy from the specified xml resource. Throws 377 * {@link InflateException} if there is an error. 378 * 379 * @param resource ID for an XML layout resource to load (e.g., 380 * <code>R.layout.main_page</code>) 381 * @param root Optional view to be the parent of the generated hierarchy (if 382 * <em>attachToRoot</em> is true), or else simply an object that 383 * provides a set of LayoutParams values for root of the returned 384 * hierarchy (if <em>attachToRoot</em> is false.) 385 * @param attachToRoot Whether the inflated hierarchy should be attached to 386 * the root parameter? If false, root is only used to create the 387 * correct subclass of LayoutParams for the root view in the XML. 388 * @return The root View of the inflated hierarchy. If root was supplied and 389 * attachToRoot is true, this is root; otherwise it is the root of 390 * the inflated XML file. 391 */ 392 public View inflate(int resource, ViewGroup root, boolean attachToRoot) { 393 if (DEBUG) System.out.println("INFLATING from resource: " + resource); 394 XmlResourceParser parser = getContext().getResources().getLayout(resource); 395 try { 396 return inflate(parser, root, attachToRoot); 397 } finally { 398 parser.close(); 399 } 400 } 401 402 /** 403 * Inflate a new view hierarchy from the specified XML node. Throws 404 * {@link InflateException} if there is an error. 405 * <p> 406 * <em><strong>Important</strong></em> For performance 407 * reasons, view inflation relies heavily on pre-processing of XML files 408 * that is done at build time. Therefore, it is not currently possible to 409 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. 410 * 411 * @param parser XML dom node containing the description of the view 412 * hierarchy. 413 * @param root Optional view to be the parent of the generated hierarchy (if 414 * <em>attachToRoot</em> is true), or else simply an object that 415 * provides a set of LayoutParams values for root of the returned 416 * hierarchy (if <em>attachToRoot</em> is false.) 417 * @param attachToRoot Whether the inflated hierarchy should be attached to 418 * the root parameter? If false, root is only used to create the 419 * correct subclass of LayoutParams for the root view in the XML. 420 * @return The root View of the inflated hierarchy. If root was supplied and 421 * attachToRoot is true, this is root; otherwise it is the root of 422 * the inflated XML file. 423 */ 424 public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { 425 synchronized (mConstructorArgs) { 426 final AttributeSet attrs = Xml.asAttributeSet(parser); 427 Context lastContext = (Context)mConstructorArgs[0]; 428 mConstructorArgs[0] = mContext; 429 View result = root; 430 431 try { 432 // Look for the root node. 433 int type; 434 while ((type = parser.next()) != XmlPullParser.START_TAG && 435 type != XmlPullParser.END_DOCUMENT) { 436 // Empty 437 } 438 439 if (type != XmlPullParser.START_TAG) { 440 throw new InflateException(parser.getPositionDescription() 441 + ": No start tag found!"); 442 } 443 444 final String name = parser.getName(); 445 446 if (DEBUG) { 447 System.out.println("**************************"); 448 System.out.println("Creating root view: " 449 + name); 450 System.out.println("**************************"); 451 } 452 453 if (TAG_MERGE.equals(name)) { 454 if (root == null || !attachToRoot) { 455 throw new InflateException("<merge /> can be used only with a valid " 456 + "ViewGroup root and attachToRoot=true"); 457 } 458 459 rInflate(parser, root, attrs, false); 460 } else { 461 // Temp is the root view that was found in the xml 462 View temp; 463 if (TAG_1995.equals(name)) { 464 temp = new BlinkLayout(mContext, attrs); 465 } else { 466 temp = createViewFromTag(root, name, attrs); 467 } 468 469 ViewGroup.LayoutParams params = null; 470 471 if (root != null) { 472 if (DEBUG) { 473 System.out.println("Creating params from root: " + 474 root); 475 } 476 // Create layout params that match root, if supplied 477 params = root.generateLayoutParams(attrs); 478 if (!attachToRoot) { 479 // Set the layout params for temp if we are not 480 // attaching. (If we are, we use addView, below) 481 temp.setLayoutParams(params); 482 } 483 } 484 485 if (DEBUG) { 486 System.out.println("-----> start inflating children"); 487 } 488 // Inflate all children under temp 489 rInflate(parser, temp, attrs, true); 490 if (DEBUG) { 491 System.out.println("-----> done inflating children"); 492 } 493 494 // We are supposed to attach all the views we found (int temp) 495 // to root. Do that now. 496 if (root != null && attachToRoot) { 497 root.addView(temp, params); 498 } 499 500 // Decide whether to return the root that was passed in or the 501 // top view found in xml. 502 if (root == null || !attachToRoot) { 503 result = temp; 504 } 505 } 506 507 } catch (XmlPullParserException e) { 508 InflateException ex = new InflateException(e.getMessage()); 509 ex.initCause(e); 510 throw ex; 511 } catch (IOException e) { 512 InflateException ex = new InflateException( 513 parser.getPositionDescription() 514 + ": " + e.getMessage()); 515 ex.initCause(e); 516 throw ex; 517 } finally { 518 // Don't retain static reference on context. 519 mConstructorArgs[0] = lastContext; 520 mConstructorArgs[1] = null; 521 } 522 523 return result; 524 } 525 } 526 527 /** 528 * Low-level function for instantiating a view by name. This attempts to 529 * instantiate a view class of the given <var>name</var> found in this 530 * LayoutInflater's ClassLoader. 531 * 532 * <p> 533 * There are two things that can happen in an error case: either the 534 * exception describing the error will be thrown, or a null will be 535 * returned. You must deal with both possibilities -- the former will happen 536 * the first time createView() is called for a class of a particular name, 537 * the latter every time there-after for that class name. 538 * 539 * @param name The full name of the class to be instantiated. 540 * @param attrs The XML attributes supplied for this instance. 541 * 542 * @return View The newly instantiated view, or null. 543 */ 544 public final View createView(String name, String prefix, AttributeSet attrs) 545 throws ClassNotFoundException, InflateException { 546 Constructor<? extends View> constructor = sConstructorMap.get(name); 547 Class<? extends View> clazz = null; 548 549 try { 550 if (constructor == null) { 551 // Class not found in the cache, see if it's real, and try to add it 552 clazz = mContext.getClassLoader().loadClass( 553 prefix != null ? (prefix + name) : name).asSubclass(View.class); 554 555 if (mFilter != null && clazz != null) { 556 boolean allowed = mFilter.onLoadClass(clazz); 557 if (!allowed) { 558 failNotAllowed(name, prefix, attrs); 559 } 560 } 561 constructor = clazz.getConstructor(mConstructorSignature); 562 sConstructorMap.put(name, constructor); 563 } else { 564 // If we have a filter, apply it to cached constructor 565 if (mFilter != null) { 566 // Have we seen this name before? 567 Boolean allowedState = mFilterMap.get(name); 568 if (allowedState == null) { 569 // New class -- remember whether it is allowed 570 clazz = mContext.getClassLoader().loadClass( 571 prefix != null ? (prefix + name) : name).asSubclass(View.class); 572 573 boolean allowed = clazz != null && mFilter.onLoadClass(clazz); 574 mFilterMap.put(name, allowed); 575 if (!allowed) { 576 failNotAllowed(name, prefix, attrs); 577 } 578 } else if (allowedState.equals(Boolean.FALSE)) { 579 failNotAllowed(name, prefix, attrs); 580 } 581 } 582 } 583 584 Object[] args = mConstructorArgs; 585 args[1] = attrs; 586 587 final View view = constructor.newInstance(args); 588 if (view instanceof ViewStub) { 589 // always use ourselves when inflating ViewStub later 590 final ViewStub viewStub = (ViewStub) view; 591 viewStub.setLayoutInflater(this); 592 } 593 return view; 594 595 } catch (NoSuchMethodException e) { 596 InflateException ie = new InflateException(attrs.getPositionDescription() 597 + ": Error inflating class " 598 + (prefix != null ? (prefix + name) : name)); 599 ie.initCause(e); 600 throw ie; 601 602 } catch (ClassCastException e) { 603 // If loaded class is not a View subclass 604 InflateException ie = new InflateException(attrs.getPositionDescription() 605 + ": Class is not a View " 606 + (prefix != null ? (prefix + name) : name)); 607 ie.initCause(e); 608 throw ie; 609 } catch (ClassNotFoundException e) { 610 // If loadClass fails, we should propagate the exception. 611 throw e; 612 } catch (Exception e) { 613 InflateException ie = new InflateException(attrs.getPositionDescription() 614 + ": Error inflating class " 615 + (clazz == null ? "<unknown>" : clazz.getName())); 616 ie.initCause(e); 617 throw ie; 618 } 619 } 620 621 /** 622 * Throw an exception because the specified class is not allowed to be inflated. 623 */ 624 private void failNotAllowed(String name, String prefix, AttributeSet attrs) { 625 throw new InflateException(attrs.getPositionDescription() 626 + ": Class not allowed to be inflated " 627 + (prefix != null ? (prefix + name) : name)); 628 } 629 630 /** 631 * This routine is responsible for creating the correct subclass of View 632 * given the xml element name. Override it to handle custom view objects. If 633 * you override this in your subclass be sure to call through to 634 * super.onCreateView(name) for names you do not recognize. 635 * 636 * @param name The fully qualified class name of the View to be create. 637 * @param attrs An AttributeSet of attributes to apply to the View. 638 * 639 * @return View The View created. 640 */ 641 protected View onCreateView(String name, AttributeSet attrs) 642 throws ClassNotFoundException { 643 return createView(name, "android.view.", attrs); 644 } 645 646 /** 647 * Version of {@link #onCreateView(String, AttributeSet)} that also 648 * takes the future parent of the view being constructure. The default 649 * implementation simply calls {@link #onCreateView(String, AttributeSet)}. 650 * 651 * @param parent The future parent of the returned view. <em>Note that 652 * this may be null.</em> 653 * @param name The fully qualified class name of the View to be create. 654 * @param attrs An AttributeSet of attributes to apply to the View. 655 * 656 * @return View The View created. 657 */ 658 protected View onCreateView(View parent, String name, AttributeSet attrs) 659 throws ClassNotFoundException { 660 return onCreateView(name, attrs); 661 } 662 663 /* 664 * default visibility so the BridgeInflater can override it. 665 */ 666 View createViewFromTag(View parent, String name, AttributeSet attrs) { 667 if (name.equals("view")) { 668 name = attrs.getAttributeValue(null, "class"); 669 } 670 671 if (DEBUG) System.out.println("******** Creating view: " + name); 672 673 try { 674 View view; 675 if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs); 676 else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs); 677 else view = null; 678 679 if (view == null && mPrivateFactory != null) { 680 view = mPrivateFactory.onCreateView(parent, name, mContext, attrs); 681 } 682 683 if (view == null) { 684 if (-1 == name.indexOf('.')) { 685 view = onCreateView(parent, name, attrs); 686 } else { 687 view = createView(name, null, attrs); 688 } 689 } 690 691 if (DEBUG) System.out.println("Created view is: " + view); 692 return view; 693 694 } catch (InflateException e) { 695 throw e; 696 697 } catch (ClassNotFoundException e) { 698 InflateException ie = new InflateException(attrs.getPositionDescription() 699 + ": Error inflating class " + name); 700 ie.initCause(e); 701 throw ie; 702 703 } catch (Exception e) { 704 InflateException ie = new InflateException(attrs.getPositionDescription() 705 + ": Error inflating class " + name); 706 ie.initCause(e); 707 throw ie; 708 } 709 } 710 711 /** 712 * Recursive method used to descend down the xml hierarchy and instantiate 713 * views, instantiate their children, and then call onFinishInflate(). 714 */ 715 void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, 716 boolean finishInflate) throws XmlPullParserException, IOException { 717 718 final int depth = parser.getDepth(); 719 int type; 720 721 while (((type = parser.next()) != XmlPullParser.END_TAG || 722 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { 723 724 if (type != XmlPullParser.START_TAG) { 725 continue; 726 } 727 728 final String name = parser.getName(); 729 730 if (TAG_REQUEST_FOCUS.equals(name)) { 731 parseRequestFocus(parser, parent); 732 } else if (TAG_INCLUDE.equals(name)) { 733 if (parser.getDepth() == 0) { 734 throw new InflateException("<include /> cannot be the root element"); 735 } 736 parseInclude(parser, parent, attrs); 737 } else if (TAG_MERGE.equals(name)) { 738 throw new InflateException("<merge /> must be the root element"); 739 } else if (TAG_1995.equals(name)) { 740 final View view = new BlinkLayout(mContext, attrs); 741 final ViewGroup viewGroup = (ViewGroup) parent; 742 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); 743 rInflate(parser, view, attrs, true); 744 viewGroup.addView(view, params); 745 } else { 746 final View view = createViewFromTag(parent, name, attrs); 747 final ViewGroup viewGroup = (ViewGroup) parent; 748 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); 749 rInflate(parser, view, attrs, true); 750 viewGroup.addView(view, params); 751 } 752 } 753 754 if (finishInflate) parent.onFinishInflate(); 755 } 756 757 private void parseRequestFocus(XmlPullParser parser, View parent) 758 throws XmlPullParserException, IOException { 759 int type; 760 parent.requestFocus(); 761 final int currentDepth = parser.getDepth(); 762 while (((type = parser.next()) != XmlPullParser.END_TAG || 763 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { 764 // Empty 765 } 766 } 767 768 private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs) 769 throws XmlPullParserException, IOException { 770 771 int type; 772 773 if (parent instanceof ViewGroup) { 774 final int layout = attrs.getAttributeResourceValue(null, "layout", 0); 775 if (layout == 0) { 776 final String value = attrs.getAttributeValue(null, "layout"); 777 if (value == null) { 778 throw new InflateException("You must specifiy a layout in the" 779 + " include tag: <include layout=\"@layout/layoutID\" />"); 780 } else { 781 throw new InflateException("You must specifiy a valid layout " 782 + "reference. The layout ID " + value + " is not valid."); 783 } 784 } else { 785 final XmlResourceParser childParser = 786 getContext().getResources().getLayout(layout); 787 788 try { 789 final AttributeSet childAttrs = Xml.asAttributeSet(childParser); 790 791 while ((type = childParser.next()) != XmlPullParser.START_TAG && 792 type != XmlPullParser.END_DOCUMENT) { 793 // Empty. 794 } 795 796 if (type != XmlPullParser.START_TAG) { 797 throw new InflateException(childParser.getPositionDescription() + 798 ": No start tag found!"); 799 } 800 801 final String childName = childParser.getName(); 802 803 if (TAG_MERGE.equals(childName)) { 804 // Inflate all children. 805 rInflate(childParser, parent, childAttrs, false); 806 } else { 807 final View view = createViewFromTag(parent, childName, childAttrs); 808 final ViewGroup group = (ViewGroup) parent; 809 810 // We try to load the layout params set in the <include /> tag. If 811 // they don't exist, we will rely on the layout params set in the 812 // included XML file. 813 // During a layoutparams generation, a runtime exception is thrown 814 // if either layout_width or layout_height is missing. We catch 815 // this exception and set localParams accordingly: true means we 816 // successfully loaded layout params from the <include /> tag, 817 // false means we need to rely on the included layout params. 818 ViewGroup.LayoutParams params = null; 819 try { 820 params = group.generateLayoutParams(attrs); 821 } catch (RuntimeException e) { 822 params = group.generateLayoutParams(childAttrs); 823 } finally { 824 if (params != null) { 825 view.setLayoutParams(params); 826 } 827 } 828 829 // Inflate all children. 830 rInflate(childParser, view, childAttrs, true); 831 832 // Attempt to override the included layout's android:id with the 833 // one set on the <include /> tag itself. 834 TypedArray a = mContext.obtainStyledAttributes(attrs, 835 com.android.internal.R.styleable.View, 0, 0); 836 int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID); 837 // While we're at it, let's try to override android:visibility. 838 int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1); 839 a.recycle(); 840 841 if (id != View.NO_ID) { 842 view.setId(id); 843 } 844 845 switch (visibility) { 846 case 0: 847 view.setVisibility(View.VISIBLE); 848 break; 849 case 1: 850 view.setVisibility(View.INVISIBLE); 851 break; 852 case 2: 853 view.setVisibility(View.GONE); 854 break; 855 } 856 857 group.addView(view); 858 } 859 } finally { 860 childParser.close(); 861 } 862 } 863 } else { 864 throw new InflateException("<include /> can only be used inside of a ViewGroup"); 865 } 866 867 final int currentDepth = parser.getDepth(); 868 while (((type = parser.next()) != XmlPullParser.END_TAG || 869 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { 870 // Empty 871 } 872 } 873 874 private static class BlinkLayout extends FrameLayout { 875 private static final int MESSAGE_BLINK = 0x42; 876 private static final int BLINK_DELAY = 500; 877 878 private boolean mBlink; 879 private boolean mBlinkState; 880 private final Handler mHandler; 881 882 public BlinkLayout(Context context, AttributeSet attrs) { 883 super(context, attrs); 884 mHandler = new Handler(new Handler.Callback() { 885 @Override 886 public boolean handleMessage(Message msg) { 887 if (msg.what == MESSAGE_BLINK) { 888 if (mBlink) { 889 mBlinkState = !mBlinkState; 890 makeBlink(); 891 } 892 invalidate(); 893 return true; 894 } 895 return false; 896 } 897 }); 898 } 899 900 private void makeBlink() { 901 Message message = mHandler.obtainMessage(MESSAGE_BLINK); 902 mHandler.sendMessageDelayed(message, BLINK_DELAY); 903 } 904 905 @Override 906 protected void onAttachedToWindow() { 907 super.onAttachedToWindow(); 908 909 mBlink = true; 910 mBlinkState = true; 911 912 makeBlink(); 913 } 914 915 @Override 916 protected void onDetachedFromWindow() { 917 super.onDetachedFromWindow(); 918 919 mBlink = false; 920 mBlinkState = true; 921 922 mHandler.removeMessages(MESSAGE_BLINK); 923 } 924 925 @Override 926 protected void dispatchDraw(Canvas canvas) { 927 if (mBlinkState) { 928 super.dispatchDraw(canvas); 929 } 930 } 931 } 932} 933