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(" 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) {
return new LayoutParams(getContext(), attrs);
}
public LayoutParams(Context c, AttributeSet attrs) {
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
setBaseAttributes(a,
R.styleable.ViewGroup_Layout_layout_width,
R.styleable.ViewGroup_Layout_layout_height);
a.recycle();
}
这个方法被 ViewGroup
的子类重写后,会解析 xml 中更多的布局参数,例如在 LinearLayout
中重写后,还会解析 layout_weight
和 layout_gravity
参数。
在第 24 行 if (!attachToRoot)
判断,因为这里的 attachToRoot
取值为 false
,所以判断为 true
,进入 if
分支,到达第 25 行 temp.setLayoutParams(params);
,把布局参数设置给了根节点控件对象。
在第 34 行 if (root != null && attachToRoot)
判断,由于 attachToRoot
为 false
,所以判断为 false
,不会进入 if
语句,也就是说不会把根节点控件对象以及布局参数设置给 root
。
在第 39 行 if (root == null || !attachToRoot)
判断,由于 attachToRoot
为 false
,所以判断为 true
,进入 if
语句,到达第 40 行 result = temp;
,也就是把根节点控件对象赋值给了 result
变量。
在第 43 行,return result;
,返回的就是根节点对象。
总结一下:
根节点是否是 merge | 取值组合 | ViewGroup root | boolean attachToRoot | 返回值 |
---|---|---|---|---|
否 | 第一组 | notNull | false | 返回的是 xml 布局的根节点 View 对象,并且对象上拥有根节点上的布局参数。 |
2.1.2 根节点不是 merge 时,第二组取值情况分析
根节点是否是 merge | 取值组合 | ViewGroup root | boolean attachToRoot |
---|---|---|---|
否 | 第二组 | notNull | true |
我们直接从第 24 行开始,因为之前的代码流程和第一组取值情况是一模一样的。
在第 24 行,if (!attachToRoot)
判断,由于 attachToRoot
的取值为 true
,所以判断为 false
,不会进入 if
分支,也就是说不会把布局参数设置给了根节点控件对象。
在第 34 行 if (root != null && attachToRoot)
判断,由于 root
不为 null
并且 attachToRoot
为 true
,所以判断为 true
,会进入 if
语句,第 35 行:root.addView(temp, params);
,也就是说会把根节点控件对象以及布局参数设置给 root
。
在第 39 行 if (root == null || !attachToRoot)
判断,因为 root
不为 null
且 attachToRoot
不为 false
,所以判断为 false
,不会进入此分支。
在第 43 行 return result;
,result
是在第 5 行被赋值为 root
,没有被重新赋值,所以返回的是 root
。
小结一下:
根节点是否是 merge | 取值组合 | ViewGroup root | boolean attachToRoot | 返回值 |
---|---|---|---|---|
否 | 第二组 | notNull | true | 返回的是添加了根节点 View 对象以及布局参数的 root 对象 |
2.1.3 根节点不是 merge 时,第三组取值情况分析
根节点是否是 merge | 取值组合 | ViewGroup root | boolean attachToRoot |
---|---|---|---|
否 | 第三组 | null | false |
在第 5 行 View result = root;
把 root
的值赋值给 View result
,那么有 result
的值是 null
。
在第 21 行 if (root != null)
判断,因为 root
为 null
,所以判断为 false
,不会进入 if
分支,也就是说 ViewGroup.LayoutParams params
的值仍然是 null
,没有发生变化。
在第 34 行 if (root != null && attachToRoot)
判断,因为 root
为 null
,所以判断为 false
,不会进入 if
分支,也就是说,不会把根节点控件对象以及布局参数设置给 root
。
在第 39 行 if (root == null || !attachToRoot)
判断,因为 root
为 null
,所以判断为 true
,进入 if
分支,到达第 40 行,result = temp;
,把根节点控件对象 temp
赋值给了 View result
变量。
在第 43 行 return result;
,返回的是谁呢?返回的是没有布局参数的根节点控件对象。
小结一下:
根节点是否是 merge | 取值组合 | ViewGroup root | boolean attachToRoot | 返回值 |
---|---|---|---|---|
否 | 第三组 | null | false | 返回的是没有布局参数信息的根节点 View 对象 |
2.1.4 根节点不是 merge 时,第四组取值情况分析
根节点是否是 merge | 取值组合 | ViewGroup root | boolean attachToRoot |
---|---|---|---|
否 | 第四组 | null | true |
我们直接从第 34 行开始,因为之前的代码流程和第三组是一模一样的。
在第 34 行,if (root != null && attachToRoot)
判断,因为 root
为 null
,所以判断为 false
,不会进入 if
分支,也就是说,不会把根节点控件对象以及布局参数设置给 root
。
在第 39 行 if (root == null || !attachToRoot)
判断,因为 root
为 null
,所以判断为 true
,进入 if
分支,到达第 40 行,result = temp;
,把根节点控件对象 temp
赋值给了 View result
变量。
在第 43 行 return result;
,返回的是谁呢?返回的是没有布局参数的根节点控件对象。
第四组取值情况和第三组的返回值是一样的。
2.1.5 根节点为 merge 时情况分析
在第 9 行 if (TAG_MERGE.equals(name))
判断,是 merge
根节点,进入 if
分支;
在第 11 行 if (root == null || !attachToRoot)
判断,若 root
为 null
,或者 attachToRoot
为 false
,判断都会成立,进入 if
语句后抛出异常。
throw new InflateException(" can be used only with a valid "
- “ViewGroup root and attachToRoot=true”);
这就是提醒我们,当根节点是 merge
时,root
必须不为 null
而且 attachToRoot
必须为 true
。
在第 43 行 return result;
,而 result
在第 5 行 View result = root;
被赋值为 root
。
总结一下取值情况:
根节点是否是 merge | 取值组合 | ViewGroup root | boolean attachToRoot | 返回值 |
---|---|---|---|---|
否 | 第一组 | notNull | false | 返回的是 xml 布局的根节点 View 对象,并且对象上拥有根节点上的布局参数。 |
否 | 第二组 | notNull | true | 返回的是添加了根节点 View 对象以及布局参数的 root 对象。 |
否 | 第三组 | null | false | 返回的是没有布局参数信息的根节点 View 对象。 |
否 | 第四组 | null | true | 返回的是没有布局参数信息的根节点 View 对象。 |
是 | notNull (必须) | true (必须) | 返回的是 root 对象。 |
2.2 实际应用
2.2.1 自定义控件填充布局
需要填充的布局 custom_view_layout.xml
如下:
CustomView
类如下:
public class CustomView extends LinearLayout {
public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.custom_view_layout, this);
}
}
这里的 inflate()
方法是 View
类的静态方法:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
内部调用的是 LayoutInflater
的第一个 inflate()
方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
ViewGroup root
不为 null
且 boolean attachToRoot
为 true
,根节点不是 merge
标签,所以对应的是表格里的第二组情况,返回的是添加了根节点 View
对象以及布局参数的 root
对象,也就是说根节点 View
对象已经添加进入了 root
对象里面。
这里,我们使用 Android Studio 的 Layout Inspector 工具(在 Tools -> Layout Inspector 开启)来查看一下布局:
可以看到出现了重复布局。我们知道,merge
标签可以用于优化重复布局。
现在我们修改布局文件为 custom_merge_view_layout.xml
:
代码中填充修改后的布局:
public class CustomMergeView extends LinearLayout {
public CustomMergeView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.custom_merge_view_layout, this);
}
}
再次使用布局查看器查看布局:
可以看到使用 merge
标签消除了重复布局。
2.2.2 Fragment 填充布局
新建一个 FragmentInflateActivity.java
文件:
public class FragmentInflateActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_inflate_activity);
getSupportFragmentManager().beginTransaction()
.add(R.id.fl_container, MyFragment.newInstance())
.commit();
}
public static void start(Context context) {
Intent starter = new Intent(context, FragmentInflateActivity.class);
context.startActivity(starter);
}
}
对应的 fragment_inflate_activity.xml
:
MyFragment.java
如下:
public class MyFragment extends Fragment {
private static final String TAG = “MyFragment”;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
在这里小编整理了一份Android大厂常见面试题,和一些Android架构视频解析,都已整理成文档,全部都已打包好了,希望能够对大家有所帮助,在面试中能顺利通过。
喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
“https://i-blog.csdnimg.cn/blog_migrate/2ccc2b71fa4d116de5b1974f107168df.jpeg” />
最后
在这里小编整理了一份Android大厂常见面试题,和一些Android架构视频解析,都已整理成文档,全部都已打包好了,希望能够对大家有所帮助,在面试中能顺利通过。
[外链图片转存中…(img-kyW1JI9s-1713686254758)]
[外链图片转存中…(img-0ewJIrYE-1713686254759)]
喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!