引言
merge標簽是作為include標簽的一種輔助擴展來使用的,它的主要作用是為了防止在引用布局文件時產生多余的布局嵌套。大家都知道,Android去解析和展示一個布局是需要消耗時間的,布局嵌套的越多,那么解析起來就越耗時,性能也就越差,因此我們在編寫布局文件時應該讓嵌套的層數越少越好。
include標簽的缺點
在 Android布局總結三:include總結 我們講解include標簽的用法時主要介紹了它優點,但是它也存在着一個不好的地方,就是可能會導致產生多余的布局嵌套。這里還是通過舉例的方式跟大家說明一下,比如說我們需要編寫一個確定取消按鈕的公共布局,這樣任何一個界面需要確定和取消功能時就不用再單獨編寫了,新建ok_cancel_layout.xml,代碼如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <Button android:id="@+id/ok" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:text="OK" /> <Button android:id="@+id/cancel" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:layout_marginTop="10dp" android:text="Cancel" /> </LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
可以看到,這個界面也是非常簡單,外層是一個垂直方向的LinearLayout,LinearLayout中包含了兩個按鈕,一個用於實現確定功能,一個用於實現取消功能。現在我們可以來預覽一下這個界面,如下圖所示:

好的,然后我們有一個profile.xml的界面需要編輯一些內容,那么這里就可以將ok_cancel_layout這個布局引入到profile.xml界面當中,如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <EditText android:id="@+id/edit" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:layout_marginTop="10dp" android:hint="Edit something here" /> <include layout="@layout/ok_cancel_layout"/> </LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
在profile.xml當中有一個EditText控件用於編輯內容,然后下面使用了標簽來將ok_cancel_layout布局進行引入,現在重新運行一下程序,界面效果如下圖所示:

看上去效果非常不錯對嗎?可是在你毫無察覺的情況下,目前profile.xml這個界面當中其實已經存在着多余的布局嵌套了!感覺還沒寫幾行代碼呢,怎么這就已經有多余的布局嵌套了?不信的話我們可以通過View Hierarchy工具來查看一下,如下圖所示:

可以看到,最外層首先是一個FrameLayout,這個無可厚非,不知道為什么最外層是FrameLayout的朋友可以去參考 Android LayoutInflater原理分析,帶你一步步深入了解View(一) 這篇文章。然后FrameLayout中包含的是一個LinearLayout,這個就是我們在profile.xml中定義的最外層布局。接下來的部分就有問題了,在最外層的LinearLayout當中包含了兩個元素,一個是EditText,另一個又是一個LinearLayout,然后在這個內部的LinearLayout當中才包含了確定和取消這兩個按鈕。
使用merge標簽
相信大家已經可以看出來了吧,這個內部的LinearLayout就是一個多余的布局嵌套,實際上並不需要這樣一層,讓兩個按鈕直接包含在外部的LinearLayout當中就可以了。而這個多余的布局嵌套其實就是由於布局引入所導致的,因為我們在ok_cancel_layout.xml中也定義了一個LinearLayout。那么應該怎樣優化掉這個問題呢?當然就是使用merge標簽來完成了,修改ok_cancel_layout.xml中的代碼,如下所示:
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <Button android:id="@+id/ok" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:text="OK" /> <Button android:id="@+id/cancel" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:layout_marginTop="10dp" android:text="Cancel" /> </merge>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
可以看到,這里我們將ok_cancel_layout最外層的LinearLayout布局刪除掉,換用了merge標簽,這就表示當有任何一個地方去include這個布局時,會將merge標簽內包含的內容直接填充到include的位置,不會再添加任何額外的布局結構。好的,merge的用法就是這么簡單,現在重新運行一下程序,你會看到界面沒有任何改變,然后我們再通過View Hierarchy工具來查看一下當前的View結構,如下圖所示:

merge標簽原理
其實就是減少在include布局文件時的層級。標簽是這幾個標簽中最讓我費解的,大家可能想不到,標簽竟然會是一個Activity,里面有一個LinearLayout對象。
/** * Exercise <merge /> tag in XML files. */ public class Merge extends Activity { private LinearLayout mLayout; @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); mLayout = new LinearLayout(this); mLayout.setOrientation(LinearLayout.VERTICAL); LayoutInflater.from(this).inflate(R.layout.merge_tag, mLayout); setContentView(mLayout); } public ViewGroup getLayout() { return mLayout; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
使用merge來組織子元素可以減少布局的層級。例如我們在復用一個含有多個子控件的布局時,肯定需要一個ViewGroup來管理,例如這樣 :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dip" android:layout_gravity="center_horizontal|bottom" android:padding="12dip" android:background="#AA000000" android:textColor="#ffffffff" android:text="Golden Gate" /> </FrameLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
將該布局通過include引入時就會多引入了一個FrameLayout層級,此時結構如下 :

使用merge標簽就會消除上圖中藍色的FrameLayout層級。示例如下 :
<merge xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dip" android:layout_gravity="center_horizontal|bottom" android:padding="12dip" android:background="#AA000000" android:textColor="#ffffffff" android:text="Golden Gate" /> </merge>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
效果圖如下 :

那么它是如何實現的呢,我們還是看源碼吧。相關的源碼也是在LayoutInflater的inflate()函數中。
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; 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(); // m如果是erge標簽,那么調用rInflate進行解析 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"); } // 解析merge標簽 rInflate(parser, root, attrs, false); } else { // 代碼省略 } } catch (XmlPullParserException e) { // 代碼省略 } return result; } } void rInflate(XmlPullParser parser, View parent, final 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) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_INCLUDE.equals(name)) { // 代碼省略 parseInclude(parser, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else if (TAG_1995.equals(name)) { final View view = new BlinkLayout(mContext, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs, true); viewGroup.addView(view, params); } else { // 我們的例子會進入這里 final View view = createViewFromTag(parent, name, attrs); // 獲取merge標簽的parent final ViewGroup viewGroup = (ViewGroup) parent; // 獲取布局參數 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); // 遞歸解析每個子元素 rInflate(parser, view, attrs, true); // 將子元素直接添加到merge標簽的parent view中 viewGroup.addView(view, params); } } if (finishInflate) parent.onFinishInflate(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
如果使用include標簽,那么直接將其中的子元素添加到merge標簽parent中,這樣就保證了不會引入額外的層級