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