目录
1. 前言
在开发中,对于 LayoutInflater
的 inflate()
方法,它的作用是把 xml 布局转换为对应的 View
对象,我们几乎天天在用。但是,对于 inflate()
方法的参数,是比较令人迷惑的。即便是看了文档的解释,依然不能解开迷惑。
或许,每次使用只能采取试验的办法,也不会非常影响开发;或许,记住在具体场景该怎么传递具体的参数,而不明所以。
我们不应该忽略细节:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
ViewGroup root
参数的作用是什么,为什么有时候可以传null
,有时候却不可以?boolean attachToRoot
参数什么时候传true
,什么时候传false
?为什么有时候传递true
会崩溃?- 为什么有的时候 xml 中根节点设置的布局参数却不生效?
这篇文章主要是说明 inflate()
方法参数的含义,以及在具体场景的使用。
2. 正文
2.1 inflate() 方法分析
在 LayoutInflater
类中,有几个重载的 inflate()
方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
public View inflate(XmlPullParser parser, @Nullable ViewGroup root)
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
大家在实际开发中,使用比较多的应该是前两个。
它们的调用关系(箭头指向表示调用方向)如下:
从图中可以看出前三个最终调用的都是最后一个:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
那么现在,我们集中精力去看最后一个方法。把最后一个方法搞清楚就可以了,就可以搞懂另外三个了。
这个方法中的第一个参数 XmlPullParser parser
,查看源码,可以看到:
final Resources res = getContext().getResources();
XmlResourceParser parser = res.getLayout(resource);
是由 xml 转换而来的,用来对 xml 进行解析的一个类。
好了,我们已经了解了第一个参数的含义,就是传递要转换的 xml 布局过来。
接着看后面的两个参数:@Nullable ViewGroup root
和 boolean attachToRoot
。需要注意的是 ViewGroup root
前面有一个注解 @Nullable
,表示 ViewGroup root
这个参数可以为 null
。
这两个参数的取值组合有几种呢?4 种。
取值组合 | ViewGroup root | boolean attachToRoot |
---|---|---|
第一组 | notNull | false |
第二组 | notNull | true |
第三组 | null | false |
第四组 | null | true |
不同的取值组合,对于最后的返回值 View
有什么影响呢?
到这里,我们需要去查看一下 inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
方法的源码:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
View result = root;
advanceToRootNode(parser);
// 获取根节点的名字,比如 LinearLayout, FrameLayout 等。
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
// 根节点的名字是 merge
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
// 获取 xml 布局的根 View 对象,比如 LinearLayout 对象,FrameLayout 对象等。
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
}
我们先不考虑根节点为 merge
的情况,因为这是比较特殊的根节点。先按照一般的情况来分析,有助于解决普遍的问题。
2.1.1 根节点不是 merge 时,第一组取值情况分析
根节点是否是 merge | 取值组合 | ViewGroup root | boolean attachToRoot |
---|---|---|---|
否 | 第一组 | notNull | false |
在第 5 行 View result = root;
把 root
的值赋值给 View result
,那么有 result
的值是 notNull
。
在第 21 行 if (root != null)
的判断语句判断为 true
,不能进入 if
语句。
在第 23 行 params = root.generateLayoutParams(attrs);
,通过 root
来获取根节点的布局参数 ViewGroup.LayoutParams
对象,也就是说,把 xml 中的根节点的 layout_
开头的属性,如layout_width
和 layout_height
对应的值转为布局参数对象中的字段值,如width
和 height
值。对应的源码在 ViewGroup
中如下:
public LayoutParams generateLayoutParams(AttributeSet attrs)