[关闭]
@linux1s1s 2019-03-12T11:13:22.000000Z 字数 7809 阅读 2019

LayoutInflater.inflate()浅析

AndroidView 2016-04


几个问题

源码

  1. /**
  2. * Inflate a new view hierarchy from the specified xml resource. Throws
  3. * {@link InflateException} if there is an error.
  4. *
  5. * @param resource ID for an XML layout resource to load (e.g.,
  6. * <code>R.layout.main_page</code>)
  7. * @param root Optional view to be the parent of the generated hierarchy.
  8. * @return The root View of the inflated hierarchy. If root was supplied,
  9. * this is the root View; otherwise it is the root of the inflated
  10. * XML file.
  11. */
  12. public View inflate(int resource, ViewGroup root) {
  13. return inflate(resource, root, root != null);
  14. }
  15. /**
  16. * Inflate a new view hierarchy from the specified xml node. Throws
  17. * {@link InflateException} if there is an error. *
  18. * <p>
  19. * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
  20. * reasons, view inflation relies heavily on pre-processing of XML files
  21. * that is done at build time. Therefore, it is not currently possible to
  22. * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
  23. *
  24. * @param parser XML dom node containing the description of the view
  25. * hierarchy.
  26. * @param root Optional view to be the parent of the generated hierarchy.
  27. * @return The root View of the inflated hierarchy. If root was supplied,
  28. * this is the root View; otherwise it is the root of the inflated
  29. * XML file.
  30. */
  31. public View inflate(XmlPullParser parser, ViewGroup root) {
  32. return inflate(parser, root, root != null);
  33. }
  34. /**
  35. * Inflate a new view hierarchy from the specified xml resource. Throws
  36. * {@link InflateException} if there is an error.
  37. *
  38. * @param resource ID for an XML layout resource to load (e.g.,
  39. * <code>R.layout.main_page</code>)
  40. * @param root Optional view to be the parent of the generated hierarchy (if
  41. * <em>attachToRoot</em> is true), or else simply an object that
  42. * provides a set of LayoutParams values for root of the returned
  43. * hierarchy (if <em>attachToRoot</em> is false.)
  44. * @param attachToRoot Whether the inflated hierarchy should be attached to
  45. * the root parameter? If false, root is only used to create the
  46. * correct subclass of LayoutParams for the root view in the XML.
  47. * @return The root View of the inflated hierarchy. If root was supplied and
  48. * attachToRoot is true, this is root; otherwise it is the root of
  49. * the inflated XML file.
  50. */
  51. public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
  52. final Resources res = getContext().getResources();
  53. if (DEBUG) {
  54. Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
  55. + Integer.toHexString(resource) + ")");
  56. }
  57. final XmlResourceParser parser = res.getLayout(resource);
  58. try {
  59. return inflate(parser, root, attachToRoot);
  60. } finally {
  61. parser.close();
  62. }
  63. }
  64. /**
  65. * Inflate a new view hierarchy from the specified XML node. Throws
  66. * {@link InflateException} if there is an error.
  67. * <p>
  68. * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
  69. * reasons, view inflation relies heavily on pre-processing of XML files
  70. * that is done at build time. Therefore, it is not currently possible to
  71. * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
  72. *
  73. * @param parser XML dom node containing the description of the view
  74. * hierarchy.
  75. * @param root Optional view to be the parent of the generated hierarchy (if
  76. * <em>attachToRoot</em> is true), or else simply an object that
  77. * provides a set of LayoutParams values for root of the returned
  78. * hierarchy (if <em>attachToRoot</em> is false.)
  79. * @param attachToRoot Whether the inflated hierarchy should be attached to
  80. * the root parameter? If false, root is only used to create the
  81. * correct subclass of LayoutParams for the root view in the XML.
  82. * @return The root View of the inflated hierarchy. If root was supplied and
  83. * attachToRoot is true, this is root; otherwise it is the root of
  84. * the inflated XML file.
  85. */
  86. public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
  87. synchronized (mConstructorArgs) {
  88. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
  89. final AttributeSet attrs = Xml.asAttributeSet(parser);
  90. Context lastContext = (Context)mConstructorArgs[0];
  91. mConstructorArgs[0] = mContext;
  92. View result = root;
  93. try {
  94. // Look for the root node.
  95. int type;
  96. while ((type = parser.next()) != XmlPullParser.START_TAG &&
  97. type != XmlPullParser.END_DOCUMENT) {
  98. // Empty
  99. }
  100. if (type != XmlPullParser.START_TAG) {
  101. throw new InflateException(parser.getPositionDescription()
  102. + ": No start tag found!");
  103. }
  104. final String name = parser.getName();
  105. if (DEBUG) {
  106. System.out.println("**************************");
  107. System.out.println("Creating root view: "
  108. + name);
  109. System.out.println("**************************");
  110. }
  111. if (TAG_MERGE.equals(name)) {
  112. if (root == null || !attachToRoot) {
  113. throw new InflateException("<merge /> can be used only with a valid "
  114. + "ViewGroup root and attachToRoot=true");
  115. }
  116. rInflate(parser, root, attrs, false, false);
  117. } else {
  118. // Temp is the root view that was found in the xml
  119. final View temp = createViewFromTag(root, name, attrs, false);
  120. ViewGroup.LayoutParams params = null;
  121. if (root != null) {
  122. if (DEBUG) {
  123. System.out.println("Creating params from root: " +
  124. root);
  125. }
  126. // Create layout params that match root, if supplied
  127. params = root.generateLayoutParams(attrs);
  128. if (!attachToRoot) {
  129. // Set the layout params for temp if we are not
  130. // attaching. (If we are, we use addView, below)
  131. temp.setLayoutParams(params);
  132. }
  133. }
  134. if (DEBUG) {
  135. System.out.println("-----> start inflating children");
  136. }
  137. // Inflate all children under temp
  138. rInflate(parser, temp, attrs, true, true);
  139. if (DEBUG) {
  140. System.out.println("-----> done inflating children");
  141. }
  142. // We are supposed to attach all the views we found (int temp)
  143. // to root. Do that now.
  144. if (root != null && attachToRoot) {
  145. root.addView(temp, params);
  146. }
  147. // Decide whether to return the root that was passed in or the
  148. // top view found in xml.
  149. if (root == null || !attachToRoot) {
  150. result = temp;
  151. }
  152. }
  153. } catch (XmlPullParserException e) {
  154. InflateException ex = new InflateException(e.getMessage());
  155. ex.initCause(e);
  156. throw ex;
  157. } catch (IOException e) {
  158. InflateException ex = new InflateException(
  159. parser.getPositionDescription()
  160. + ": " + e.getMessage());
  161. ex.initCause(e);
  162. throw ex;
  163. } finally {
  164. // Don't retain static reference on context.
  165. mConstructorArgs[0] = lastContext;
  166. mConstructorArgs[1] = null;
  167. }
  168. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  169. return result;
  170. }
  171. }

分析问题

回顾一下上面刚开始提及的几个问题。

  • inflate()方法中第三个Boolean型参数实现什么功能
  • 请举例常见的几种情况
  • 什么情况下应该设置为true,什么情况下应该设置为false
  1. // We are supposed to attach all the views we found (int temp)
  2. // to root. Do that now.
  3. if (root != null && attachToRoot) {
  4. root.addView(temp, params);
  5. }

所以小结下来就是这样:

  1. Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false);
  2. mLinearLayout.addView(button);

===========================等价于==========================

  1. Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, true);
  1. @Override
  2. public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  3. rootView = inflater.inflate(R.layout.webview_style_inner, container, false);
  4. rootWebView = (FanliWebviewContainer) rootView.findViewById(R.id.webviewContainer);
  5. return rootView;
  6. }

上面的第三行很多人也会这么写

  1. rootView = inflater.inflate(R.layout.webview_style_inner, null);

问题:这两种写法,你更倾向于哪种,有什么区别?为什么大部分情况下这两种写法都能正常工作,而参数取为true则直接抛IllegalStateException异常。

如果ViewGroup传值为null,最起码的区别就是下面的警告
此处输入图片的描述

那么出现这种警告以后,我们有必要甩它吗?如果不care会怎样?

你的App不会挂掉,但是可能会出现异常。当你的子View没有正确的LayoutParams时,它会自己通过generateDefaultLayoutParams)计算。
你可能并不想要这些默认的LayoutParams。你在XML指定的LayoutParams会被忽略。我们可能已经指定了子View要填充父元素的宽度,但父View又wrap_content导致最终的View小很多。

所以很多人随手将ViewGroup故意置为null,为将来的bug埋下隐患,所以这里的原则就是有ViewGroup的地方还是老老实实的传下去

问题:上面留下来的,为什么第三个参数传值为true会抛IllegalStateException异常?

原因是Fragment中将View添加到Activity,是由FragmentManager来负责添加,删除,替换等,所以这些都是由FragmentManager来管理的,不需要我们手动添加View到ViewGroup,如果手动去添加,势必会导致重复添加子View,所以会抛出IllegalStateException异常就好解释了。

那么还有其他情况和此种情况类似的吗? 这里仅仅举个栗子,在BaseAdaptergetView()方法里面

  1. @Override
  2. public View getView(int position, View convertView, ViewGroup parent) {
  3. ViewHolder holder;
  4. if (convertView == null) {
  5. holder = new ViewHolder();
  6. convertView = mInflater.inflate(R.layout.list_fanli_record_item, parent false);
  7. holder.creatTimeView = (TangFontTextView) convertView.findViewById(R.id.create_time);
  8. convertView.setTag(holder);
  9. } else {
  10. holder = (ViewHolder) convertView.getTag();
  11. }
  12. return convertView;
  13. }

上面是ListView的数据适配器Adapter中适配View并重用View的典型代码,其中L7就符合上面Fragment的情况。ListView负责决定什么时候展示它的子View,这个不由我们决定。所以小结一下结论就是:在任何我们不负责将View添加进ViewGroup的情况下都应该将attachToRoot设置为false。

总结

总结一下结论如下:

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注