Android布局總結四:Merge總結


引言

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中,這樣就保證了不會引入額外的層級


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM