在Activity中,通常都是调用 setContentView() 来加载布局,其实该方法的内部也是使用LayoutInflater 来加载布局的,只不过这部分源码是internal的,不太容易查看到。
如何创建LayoutInflater实例?
三种方式:
// 方式1
LayoutInflater layoutInflater = LayoutInflater.from(context);
// 方式2
LayoutInflater layoutInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// 方式3
LayoutInflater inflater = getLayoutInflater(); //调用Activity的getLayoutInflater()
方式1其实是对方式2做的封装,内部调用了方式2来返回一个LayoutInflater;而方式3内部调用的其实就是方式2,即:
getLayoutInflater() → LayoutInflater.from(context) → context.getSystemService(xx)
得到了LayoutInflater的实例之后就可以调用它的inflate()方法来加载布局了,如下所示:
//方法1:root表示给该布局的外部再嵌套一层父布局,一般不需要,直接传null
View layout1 = layoutInflater.inflate(R.layout.xx, root);
//方法2:root同样表示给该布局嵌套父布局;
//attachToRoot为true,表示嵌套到root中,也就和方法1一样;所以需要传false才会用到方法2
//attachToRoot为false,表示不嵌套到root中,可能需要我们手动添加,即,root.addView(layout2);
//想让布局的LayoutParams起作用但又不想重复嵌套到root中,可以使用此方法,典型例子是Fragment的onCreateView()
View layout2 = layoutInflater.inflate(int resource, ViewGroup root, boolean attachToRoot)
通常,我们还会采取下面方式加载布局:
View layout = View.inflate(context, R.layout.xx, root);
View.inflate() 其实就是通过上面的
LayoutInflater.inflate(context) 创建
LayoutInflater实例,然后还是调用它的
inflate() 来加载布局。
接下来我们就从源码的角度上看一看LayoutInflater到底是如何工作的。
不管你是使用的哪个inflate()方法的重载,最终都会辗转调用到LayoutInflater的如下代码中
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final AttributeSet attrs = Xml.asAttributeSet(parser);
mConstructorArgs[0] = mContext;
View result = root;
try {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
}
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("merge can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, attrs);//循环遍历这个根布局下的子元素
} else {
View temp = createViewFromTag(name, attrs);//根据节点名来创建View对象
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
rInflate(parser, temp, attrs);
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
}
...
return result;
}
}
从这里我们就可以清楚地看出,LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的。
第23行,调用了createViewFromTag()这个方法,并把节点名和参数传了进去。看到这个方法名,我们就应该能猜到,它是用于根据节点名来创建View对象的。确实如此,在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。当然,这里只是创建出了一个根布局的实例而已,接下来会在第31行调用rInflate()方法来循环遍历这个根布局下的子元素,遍历时也是调用createViewFromTag()方法来创建View的实例,然后还会递归调用rInflate()方法来查找这个View下的子元素,每次递归完成后则将这个View添加到父布局当中。这样的话,把整个布局文件都解析完成后就形成了一个完整的DOM结构,最终会把最顶层的根布局返回。
平时我们经常使用layout_width和layout_height来设置View的大小,并且一直都能正常工作,就好像这两个属性确实是用于设置View的大小的。而实际上则不然,它们其实是用于设置View在布局中的大小的,也就是说,首先View必须存在于一个布局中,之后如果将layout_width设置成match_parent表示让View的宽度填充满布局,如果设置成wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。
参考
1、Android LayoutInflater原理分析,带你一步步深入了解View(一)