目录
一、关于LayoutInflater
三种获取方式
方式一、Activity.getLayoutInflater()
方式二、LayoutInflater.from(Context)
方式三、Context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
总结:activity通过通过调用getLayoutInflater(),实际上到PhoneWindow里间接调用LayoutInflater.from(Context),最终会调用到方式三,如下图
二、关于inflate()
四种重载
方式一:View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
方式二:View inflate(XmlPullParser parser, @Nullable ViewGroup root)
方式三:View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
方式四:View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
总结:注意它们只是参数的不同,最后都是调用到方式四。
三、关于inflate()的实验
我们研究五种情形:
inflater.inflate(R.layout.item,null)
inflater.inflate(R.layout.item, null, true);
inflater.inflate(R.layout.item, null, false);
inflater.inflate(R.layout.item, parent, true);
inflater.inflate(R.layout.item, parent, false);
先看布局activity_main.xml,也就是作为inflate的第二个参数
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</RelativeLayout>
然后再建一个布局item.xml,作为inflate的第一个参数
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#FFEB3B">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人生易老天难老,岁岁重阳" />
</LinearLayout>
我要把item.xml这个布局文件添加到我的activity的布局中,为了看到五种情形有什么不一样,我依次替换圈起来的代码,查看展示效果
情形一:inflater.inflate(R.layout.item,null)
情形二:inflater.inflate(R.layout.item, null, true);
情形三:inflater.inflate(R.layout.item, null, false);
情形四:inflater.inflate(R.layout.item, parent, true);
使用该方法时rl.addView(view);代码报错,错误异常如下
出错的原因,这是因为在源码里面执行了下面这句,这时候temp已经有父布局了,所以你在MainActivity里面又执行了parent.addView(view)的时候会在addView里面抛出异常,所以inflate()的第三个参数要么传入false,这样源码里面就不会自动addView,你可以在外面手动addView。要么传入true,但是这样你就不要在外面再手动addView了,因为会抛异常
解决方法
删除parent.addView(view);这一行代码,然后运行展示效果如下
情形五:inflater.inflate(R.layout.item, parent, false);
总结:只有inflate的第二个参数为null,xml的根布局的宽高无效;
如果inflate的第二个参数不为null,第三个参数为true,则在外面不要再给parent布局addView了;如果第三个参数为false,则需要手动给parent布局addView
标准用法是情形五
四、源码分析
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
// 获取上下文对象,即LayoutInflater.from传入的Context.
final Context inflaterContext = mContext;
// 根据parser构建XmlPullAttributes.
final AttributeSet attrs = Xml.asAttributeSet(parser);
// 保存之前的Context对象.
Context lastContext = (Context) mConstructorArgs[0];
// 保存之前的Context对象.
mConstructorArgs[0] = inflaterContext;
// 注意,默认返回的是父布局root.
View result = root;
try {
// Look for the root node.
//找到根节点,即查找xml的开始标签.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
// 如果没有找到有效的开始标签,则抛出InflateException异常.
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//获得标签的名字
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
//如果标签是merge,则root不能为空,且attachToRoot不能为false,因为merge标签是要合并到根布局的
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//解析内部View,传入的父布局为root
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
//实例化xml中的根布局
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
//设置LayoutParams为null,这个很关键,很多布局参数跟实际有出入的问题都是它导致的,xml里的LayoutParams刚解析出默认为null
ViewGroup.LayoutParams params = null;
//如果root布局(非XML)存在
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
//则根据XML根布局的属性生成LayoutParams。也就是说加入root为null,XML里的根布局的View布局参数一定为null
params = root.generateLayoutParams(attrs);
//attachToRoot为false则将LayoutParams设置给XML里的根布局
if (!attachToRoot) {
//情形五
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
如果实例化的XML根布局不需要添加到root中,则直接将根据root生成的params参数设置
// 给它即可.
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
//解析所有的子View,并把其添加到XML的根布局里
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
//如果root不为null,且attachToRoot为true则将xml根布局添加到root里
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.
//如果root为null或者attachToRoot为false则直接返回xml的根布局
//这里注意,之前我一直搞不明白为什么root!=null,attachToRoot=false时为什么
//返回的是xml的根布局,原因就在这里的||!attachToRoot
//情形一、二、三
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(parser.getPositionDescription()
+ ": " + 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;
}
}