在開(kāi)發(fā)中UI布局是我們都會(huì)遇到的問(wèn)題,隨著UI越來(lái)越多,布局的重復(fù)性、復(fù)雜度也會(huì)隨之增長(zhǎng)。對(duì)此我們優(yōu)化xml布局就不得不說(shuō)重用布局,為了有效地重新使用完整的布局,Google提出可以使用<include>和<merge>這兩個(gè)非常有用的標(biāo)簽,用以在當(dāng)前布局中嵌入另一個(gè)布局,下面我們就來(lái)逐個(gè)學(xué)習(xí)一下。
一、include
<include/>標(biāo)簽可以允許在一個(gè)布局當(dāng)中引入另外一個(gè)布局,那么比如說(shuō)我們程序的所有界面都有一個(gè)公共的部分,這個(gè)時(shí)候最好的做法就是將這個(gè)公共的部分提取到一個(gè)獨(dú)立的布局文件當(dāng)中,然后在每個(gè)界面的布局文件當(dāng)中來(lái)引用這個(gè)公共的布局。這里舉個(gè)例子吧,例如在include_layout.xml添加兩個(gè)按鈕:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button One"
android:id="@+id/button" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button Two"
android:id="@+id/button2" />
</LinearLayout>
好的,那這連個(gè)按鈕作為一個(gè)獨(dú)立的布局現(xiàn)在我們已經(jīng)編寫(xiě)完了,接下來(lái)的工作就非常簡(jiǎn)單了,無(wú)論任何界面需要加入這個(gè)布局,只需要在布局文件中引入include_layout.xml就可以了。那么比如說(shuō)我們的程序當(dāng)中有一個(gè)activity_main.xml文件,現(xiàn)在想要引入include_layout.xml只需要這樣寫(xiě):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button"
android:onClick="onClick"
android:id="@+id/button1"/>
<include layout="@layout/include_layout"/>
</LinearLayout>
顯示的效果為:

看上去效果非常不錯(cuò)對(duì)嗎? 可是在你毫無(wú)察覺(jué)的情況下,目前activity_main.xml這個(gè)界面當(dāng)中其實(shí)已經(jīng)存在著多余的布局嵌套了!感覺(jué)還沒(méi)寫(xiě)幾行代碼呢,怎么這就已經(jīng)有多余的布局嵌套了?不信的話我們可以通過(guò)UI Automator Viewer工具來(lái)查看一下,如下圖所示:
可以看到,最外層首先是一個(gè)FrameLayout,至于為什么是FrameLayout,不知道的朋友可以去參考 Android LayoutInflater原理分析,帶你一步步深入了解View(一) 這篇文章。然后FrameLayout中包含的是一個(gè)LinearLayout,這個(gè)就是我們?cè)?strong>activity_main.xml中定義的最外層布局了。
二、merge
通過(guò)上面內(nèi)容,相信大家已經(jīng)可以看出來(lái)了吧,在使用<include/>直接引入布局的時(shí)候,這個(gè)內(nèi)部的LinearLayout其實(shí)就是一個(gè)多余的布局嵌套,實(shí)際上并不需要這樣一層,讓兩個(gè)按鈕直接包含在外部的LinearLayout當(dāng)中就可以了。而這個(gè)多余的布局嵌套其實(shí)就是由于布局引入所導(dǎo)致的,因?yàn)槲覀冊(cè)?strong>include_layout.xml中也定義了一個(gè)LinearLayout。那么應(yīng)該怎樣優(yōu)化掉這個(gè)問(wèn)題呢?請(qǐng)接著往下看當(dāng)然就是使用<merge/>標(biāo)簽了,現(xiàn)在修改include_layout.xml布局如下:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button One"
android:id="@+id/button" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button Two"
android:id="@+id/button2" />
</merge>
可以看到,這里我們將include_layout.xml最外層的LinearLayout布局刪除掉,換用了<merge/>標(biāo)簽,這就表示當(dāng)有任何一個(gè)地方去include這個(gè)布局時(shí),會(huì)將<merge/>標(biāo)簽內(nèi)包含的內(nèi)容直接填充到include的位置,不會(huì)再添加任何額外的布局結(jié)構(gòu)。好的,<merge/>的用法就是這么簡(jiǎn)單,現(xiàn)在重新運(yùn)行一下程序,你會(huì)看到界面沒(méi)有任何改變,然后我們?cè)偻ㄟ^(guò)UI Automator Viewer工具來(lái)查看一下當(dāng)前的View結(jié)構(gòu),如下圖所示:
好了,可以看到,現(xiàn)在兩個(gè)按鈕都直接包含在了LinearLayout下面,我們的include_layout.xml當(dāng)中也就不存在多余的布局嵌套了。
三、ViewStub
對(duì)于ViewStub(請(qǐng)自備梯子)可以理解為僅在需要時(shí)才加載布局。
ViewStub是一個(gè)輕量級(jí)的View,它一個(gè)看不見(jiàn)的,不占布局位置,占用資源非常小的控件??梢詾閂iewStub指定一個(gè)布局,在Inflate布局的時(shí)候,只有ViewStub會(huì)被初始化,然后當(dāng)ViewStub被設(shè)置為可見(jiàn)的時(shí)候,或是調(diào)用了ViewStub.inflate()的時(shí)候,ViewStub所向的布局就會(huì)被Inflate和實(shí)例化,然后ViewStub的布局屬性都會(huì)傳給它所指向的布局。這樣,就可以使用ViewStub來(lái)方便的在運(yùn)行時(shí),要不要顯示某個(gè)布局。
但ViewStub也不是萬(wàn)能的,下面總結(jié)下ViewStub能做的事兒和什么時(shí)候該用ViewStub,什么時(shí)候該用可見(jiàn)性的控制。
首先來(lái)說(shuō)說(shuō)ViewStub的一些特點(diǎn):
- ViewStub只能Inflate一次,之后ViewStub對(duì)象會(huì)被置為空。按句話說(shuō),某個(gè)被ViewStub指定的布局被Inflate后,就不會(huì)夠再通過(guò)ViewStub來(lái)控制它了。
- ViewStub只能用來(lái)Inflate一個(gè)布局文件,而不是某個(gè)具體的View,當(dāng)然也可以把View寫(xiě)在某個(gè)布局文件中。
基于以上的特點(diǎn),那么可以考慮使用ViewStub的情況有:
- 在程序的運(yùn)行期間,某個(gè)布局在Inflate后,就不會(huì)有變化,除非重新啟動(dòng)。因?yàn)閂iewStub只能Inflate一次,之后會(huì)被置空,所以無(wú)法指望后面接著使用ViewStub來(lái)控制布局。所以當(dāng)需要在運(yùn)行時(shí)不止一次的顯示和隱藏某個(gè)布局,那么ViewStub是做不到的。這時(shí)就只能使用View的可見(jiàn)性來(lái)控制了。
- 想要控制顯示與隱藏的是一個(gè)布局文件,而非某個(gè)View。因?yàn)樵O(shè)置給ViewStub的只能是某個(gè)布局文件的Id,所以無(wú)法讓它來(lái)控制某個(gè)View。
所以,如果想要控制某個(gè)View(如Button或TextView)的顯示與隱藏,或者想要在運(yùn)行時(shí)不斷的顯示與隱藏某個(gè)布局或View,只能使用View的可見(jiàn)性來(lái)控制。接下來(lái)修改include_layout.xml布局如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button"
android:onClick="onClick"
android:id="@+id/button1"/>
<ViewStub
android:id="@+id/view_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/include_layout"/>
</LinearLayout>
現(xiàn)在看看MainActivity的代碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
void onClick(View view){
ViewStub viewStub= (ViewStub) findViewById(view_stub);
if (viewStub!=null){
viewStub.inflate();
}
}
}
最后來(lái)看看使用ViewStub的效果:

點(diǎn)擊button就會(huì)調(diào)用ViewStub的inflate();方法來(lái)加載我們需要的布局
最后使用ViewStub需要注意:
- 某些布局屬性要加在ViewStub而不是實(shí)際的布局上面,才會(huì)起作用,比如上面用的android:layout_margin*系列屬性,如果加在TextView上面,則不會(huì)起作用,需要放在它的ViewStub上面才會(huì)起作用。而ViewStub的屬性在inflate()后會(huì)都傳給相應(yīng)的布局。
附上UI Automator Viewer工具的打開(kāi)方法:AndroidSDK > tools > uiautomatorviewer.bat

最后,如果大家想要繼續(xù)學(xué)習(xí)更多關(guān)于性能優(yōu)化的技巧,可以到這個(gè)網(wǎng)址上閱讀更多內(nèi)容 http://developer.android.com/training/best-performance.html 。