一、概述
在xml布局文件中,多余的布局节点和嵌套会导致解析变慢。使用<merge>标签可以减少视图层级的嵌套,达到布局优化的效果。
什么情况下需要考虑使用Merge标签呢?
1.一个布局文件需要被添加到另一个父布局中(可以在xml中使用<include>添加,或在java代码中使用inflate()添加)时,可以使用merge作为该布局的根节点。这样,当被添加进父布局时根节点会自动被忽略,所有的子节点直接被添加到父布局。
2.布局文件的根节点是<FrameLayout>,不需要设置background或padding等属性,其作为Activity的视图时,可以使用<merge>代替<FrameLayout>(因为Activity内部会自动创建一个FrameLayout,setContentView()传入的布局会添加到FrameLayout中)。
二、代码示例
在开发中,一个通用布局经常会使用<include>标签添加到多个父布局中。
比如,现在有一个布局文件button_view.xml。
我们使用<include>标签将button_view添加到activity_main.xml中。
此时的View层级结构如图所示。
接下来,将button_view.xml的根节点修改为<merge>。
再次观察View层级结构,button_view自身的根节点已经不存在了。
三、源码分析
当我们在布局根节点中使用了<merge>标签,Android内部是如何忽略掉根节点,直接添加子View的呢?
在Android中,xml布局文件是通过LayoutInflater类中的inflate(int resource,ViewGroup root,boolean attachToRoot)方法,转化为View对象的。关于inflate()方法的内部实现,可参考文章 “inflate()方法详解和源码分析”。
进入rInflate(XmlPullParser parser,View parent,Context context,AttributeSet attrs,boolean finishInflate)方法。
在xml布局文件中,多余的布局节点和嵌套会导致解析变慢。使用<merge>标签可以减少视图层级的嵌套,达到布局优化的效果。
什么情况下需要考虑使用Merge标签呢?
1.一个布局文件需要被添加到另一个父布局中(可以在xml中使用<include>添加,或在java代码中使用inflate()添加)时,可以使用merge作为该布局的根节点。这样,当被添加进父布局时根节点会自动被忽略,所有的子节点直接被添加到父布局。
2.布局文件的根节点是<FrameLayout>,不需要设置background或padding等属性,其作为Activity的视图时,可以使用<merge>代替<FrameLayout>(因为Activity内部会自动创建一个FrameLayout,setContentView()传入的布局会添加到FrameLayout中)。
二、代码示例
在开发中,一个通用布局经常会使用<include>标签添加到多个父布局中。
比如,现在有一个布局文件button_view.xml。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/subroot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
我们使用<include>标签将button_view添加到activity_main.xml中。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/button_view"/>
</LinearLayout>
此时的View层级结构如图所示。
接下来,将button_view.xml的根节点修改为<merge>。
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/subroot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</merge>
再次观察View层级结构,button_view自身的根节点已经不存在了。
三、源码分析
当我们在布局根节点中使用了<merge>标签,Android内部是如何忽略掉根节点,直接添加子View的呢?
在Android中,xml布局文件是通过LayoutInflater类中的inflate(int resource,ViewGroup root,boolean attachToRoot)方法,转化为View对象的。关于inflate()方法的内部实现,可参考文章 “inflate()方法详解和源码分析”。
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
在inflate()方法中,将布局resource转化为XmlResourceParser对象来解析xml文件,接下来调用inflate(XmlPullParser parser,ViewGroup root,boolean attachToRoot)方法。
public View inflate(XmlPullParser parser, 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 {
......
final String name = parser.getName();
// 根布局标签是"merge"
if (TAG_MERGE.equals(name)) {
// 满足root!=null&&attachToRoot=true才行,也就是说"merge"无法独立存在,必须要添加到ViewGroup中
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
// 调用rInflate()方法
rInflate(parser, root, inflaterContext, attrs, false);
} else {// 根布局标签不是"merge",调用createViewFromTag()把根布局的View创建出来
// 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 && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
......
}
} catch (Exception e) {
......
}
return result;
}
}
方法内部,根据参数parser获取到根布局节点的名称。如果根节点是merge,会调用调用rInflate()方法,最后返回参数root;否则,直接根据根节点的名称,调用createViewFromTag()方法创建对应的View对象,返回该view对象或参数root。
进入rInflate(XmlPullParser parser,View parent,Context context,AttributeSet attrs,boolean finishInflate)方法。
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
// 循环遍历布局中的所有标签
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
......
final String name = parser.getName();
// 根据标签名称创建对应的View对象
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);
// 把View对象添加到参数parent中
viewGroup.addView(view, params);
}
......
}
rInflate()是一个通用的方法,这里紧接着上面的逻辑,以merge标签为例。方法内部,通过while循环,找出merge标签中的每一个节点,分别创建对应的View对象,直接把View对象添加到参数parent中。到这里,我们就明白了merge标签内部的View是如何被直接添加到父视图ViewGroup中。