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