在使用自定义View的过程中经常需要使用LayoutInflater.inflate()方法添加布局文件,如下是我们要添加的布局:layout_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/teal_200"
android:layout_width="200dp"
android:layout_height="200dp">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
activity_main.xml布局如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:id="@+id/root"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</LinearLayout>
MainActivity.java如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout mRoot = findViewById(R.id.root);
View view = LayoutInflater.from(this).inflate(R.layout.layout_item, null);
mRoot.addView(view);
}
}
这样就可以把layout_item.xml布局添加到activity_main.xml布局的LinearLayout下面了,下图是运行后的显示效果:
发现layout_item.xml的根布局LinearLayout的layout_width="200dp"和layout_height="200dp"失效了,这是什么原因?
我们进入inflate()方法一探究竟,该方法最终会调用如下3个参数的inflate方法:
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 {
// 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 (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
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 {
// 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) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 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);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
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.
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(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;
}
}
可以看到这里是使用Android提供的pull解析方式来解析布局文件的,其中TAG_MERGE表示<merge/>标签,createViewFromTag是根据节点名来创建View对象的,里面会先判断mFactory2 != null是否成立,如果成立,调用mFactory2来创建View,否则使用下面这段代码来反射创建View对象:
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
我们回到调用createViewFromTag的位置往下,这里会先判断root是否为空,如果不为空,通过下面这段代码给temp设置布局参数,temp即所要添加的布局的根布局:
// 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);
}
继续往下,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;
}
如果root不为null且attachToRoot为true,则通过root.addView(temp, params)添加布局;如果root为null或attachToRoot为false,直接返回temp。
看到这里大家应该明白为什么layout_item.xml的根布局的大小失效了,原因就是因为root为null,直接返回了temp。如果想要layout_item.xml的根布局的大小不失效,root就不能为null,改为View view = LayoutInflater.from(this).inflate(R.layout.layout_item, mRoot, false)即可。
下面总结一下:
对于inflate(int resource, ViewGroup root, boolean attachToRoot)的参数:
1.如果root为null,不论attachToRoot为true或false,所添加的布局的根布局将失效;
2.如果root不为null且attachToRoot为true,待添加的布局将自动添加到root中;
3.如果root不为null且attachToRoot为false,待添加的布局不会自动添加到root,此时还需要手动addView();