inflater.inflate(R.layout.layout_inflate_test,null);
inflater.inflate(R.layout.layout_inflate_test, root,false);
inflater.inflate(R.layout.layout_inflate_test, root,true);
做Android
这么久,经常会看到上面三个方法,只知道这是通过布局资源id
解析xml
文件并返回View
用的,但具体什么时候该用哪种参数的方法,还是懵懵懂懂。
以下从源码角度分析一下inflate()
方法。
先看一下inflate()
涉及的相关源码(以Android API 29为例):
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
//...
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
advanceToRootNode(parser);
final String name = parser.getName();
//...
if (TAG_MERGE.equals(name)) {
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
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;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(inflaterContext, attrs)
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
以上虽然有四个重载方法,但仔细观察,发现万剑归宗,最后的调用都指向一个方法:inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
,我们只需要跟进这个方法便可。
从上图可以看出以上四个方法总体上可以分为两类:第一类的第一个参数为布局资源
id
;第二类第一个参数为xml
文件解析器XmlPullParser
。
第一类调用第二类的关键代码如下:XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); }
跟进inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
方法:
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
这段代码主要通过XmlPuulPaser
获取xml
的属性集,以及初始化一些数据,把root
参数(可能为null
)赋值给返回结果。继续分析:
advanceToRootNode(parser);
// ↓(实现)
private void advanceToRootNode(XmlPullParser parser)
throws InflateException, IOException, XmlPullParserException {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
}
这里的代码含义是遍历寻找根节点,继续分析:
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
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);
}
当根节点的标签是merge
时,如果root
为null
或者attachToRoot
为false
会直接抛异常,也就是当根标签为merge
的时候必须使用inflater.inflate(R.layout.layout_inflate_test, root,true);
这种形式,不然会报错,你可以自己试验一下。实验结果:
然后看rInflate(parser, root, inflaterContext, attrs, false);
方法的实现:
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException(" cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException(" must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
//个人理解:这里的应该是控制递归调用结束的一个非常关键的因素(即非`ViewGroup`类型的view不会再被递归)
parent.onFinishInflate();
}
}
该方法递归遍历xml
文件,根据标签Tag
的名称通过createViewFromTag(parent, name, context, attrs);
方法并利用反射创建子View
,并添加到ViewGroup parent
上。然后继续分析:
else {
// Temp is the root view that was found in the xml
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;
}
}
之前的merge
是特殊情况,常见的是else
中的情况,首先会通过createViewFromTag(root, name, inflaterContext, attrs);
方法生成一个叫temp
的root view
,下面的代码是关键:
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);
}
}
如果root != null
的话,根据root
创建相应的LayoutParams params
;如果attachToRoot
为 false
的话,就把这个布局参数设置给temp
,再继续分析:
// 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;
}
首先是递归解析子布局并生成相应的View
,如果root != null && attachToRoot
的话,就把temp
以root
的布局参数params
添加到root
布局中;如果root == null
或者 attachToRoot
为false
,则忽略root
的布局参数信息,直接将temp
赋值给result
并最终返回该结果。
引用郭神的结论:
resourceId | Root | attachToRoot | 效果 |
---|---|---|---|
存在 | null | 设置无意义 | attachToRoot 失去意义 |
存在 | !=null | true | 会给加载的布局文件指定一个父布局root |
存在 | !=null | false | 则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效 |
存在 | !=null | 未设置(true) | 会给加载的布局文件指定一个父布局root |
补充说明:
- 如果
root
不为null
,布局文件最外层的layout
关于LayoutParams
设置的属性和其他属性都会被保留下来,attachToRoot
设为true
,则会给加载的布局文件的指定一个父布局,我们不需要自己再addView
,否则会报错;attachToRoot
设为false
,需要我们自己addView
,root
为null
时,被加载的布局LayoutParams
的属性会被改变,但是其它属性例如背景颜色什么的会被保留。
本文参考:Android inflate方法总结