LayoutInflater用来把一个xml文件实例化成对应的View对象。
可以使用Activity#getLayoutInflater()}
或者Context#getSystemService
来获取一个标准的LayoutInflater实例。
Activity的getLayoutInflater方法
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
PhoneWindow的getLayoutInflater方法
public LayoutInflater getLayoutInflater() {
return mLayoutInflater;
}
LayoutInflater的from方法内部就是使用context.getSystemService来获取LayoutInflater实例的。
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
获取到了LayoutInflater实例以后,就可以调用它的inflate方法来加载布局文件了。举个例子先
这是Activity的布局文件,只有一个ConstraintLayout。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/clRoot"
tools:context=".activity.LayoutInflateActivity">
</android.support.constraint.ConstraintLayout>
创建一个布局文件button_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:text="Button"
android:textAllCaps="false"
android:layout_height="match_parent">
</Button>
这段代码是kotlin写的
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_layout_inflate)
//加载布局文件
val button = layoutInflater.inflate(R.layout.button_layout, null)
//将生成的view对象加入到activity的根布局文件中
clRoot.addView(button)
}
button_layout.png
接下来看看inflate方法究竟是怎么把布局文件转换成view对象的。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
//调用3个参数的重载函数
return inflate(resource, root, root != null);
}
/**
* 根据指定的xml布局文件中生成一个view层级。有错误抛出InflateException。
* @param resource xml布局文件ID
* @param root 可选的view,如果attachToRoot是true,使用root作为生成的view层级的父布局;
* 如果attachToRoot是false,为返回的view层级的根view提供一系列的LayoutParams值。
* @param attachToRoot 用来标志生成的view层级是否应该被添加到root中去。如果是false,
* root只是用来为返回的view层级的根view创建正确的LayoutParams。
* @return 返回生成的view层级的根view。如果root不为null并且attachToRoot 是true,就返回root;
* 否则返回view层级的根view。
* */
public View inflate(int resource, ViewGroup root, boolean attachToRoot){
final Resources res = getContext().getResources();
//...
//根据资源id生成xml解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
//注释1处
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
注释1处
public View inflate(XmlPullParser parser,ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
//...
//将root赋值给result
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//...
final String name = parser.getName();
//处理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");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//注释1处
//temp是xml文件中的根view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
//注释2处
//创建与root匹配的布局参数
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//注释3处
// 将布局参数设置给temp
temp.setLayoutParams(params);
}
}
// 注释4处,填充temp下面的所有子view
rInflateChildren(parser, temp, attrs, true);
// 注释5处,如果条件满足,应该把temp添加到root中去。
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 注释6处,判断是返回root还是返回result。
if (root == null || !attachToRoot) {
result = temp;
}
}
}
//...
return result;
}
}
在注释1处,调用createViewFromTag方法获取xml文件中的根view。
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
方法内部使用反射的方式创建对应的view并返回,这里就不去细去看了。
在注释2处,创建和root匹配的布局参数。
在注释3处,如果attachToRoot为fasle,则将布局参数设置给temp。
在注释4处,调用rInflateChildren方法递归填充temp下面的所有子view。
在注释5处,如果root != null 并且 attachToRoot,就把temp添加到root 中。
在注释6处,root == null 或者!attachToRoot,就把temp赋值给result。
最后方法返回result。
接下来看一下rInflateChildren方法
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
看一下rInflate方法
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)) {
//处理include标签
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
//注释1处
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//注释2处
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
在注释1处同样调用createViewFromTag创建view对象。
在注释2处调用rInflateChildren方法递归填充所有view。
网友评论