在前面幾篇文章當中,咱們學習瞭如何經過合理管理內存,以及高性能編碼技巧的方式來提高應用程序的性能。然而實際上界面佈局也會對應用程序的性能產生比較大的影響,若是佈局寫得糟糕的話,那麼程序加載UI的速度就會很是慢,從而形成很差的用戶體驗。那麼本篇文章咱們就來學習一下,如何經過優化佈局來提供應用程序的性能。
html
Android系統中已經提供了很是多好用的控件,這讓咱們在編寫佈局的時候能夠很輕鬆。可是有些時候咱們可能須要反覆利用某個已經寫好的佈局,若是你老是使用複製粘貼的方式來進行佈局重用,這顯然是一種很笨的作法。而Android固然也已經充分考慮到了佈局重用的重要性,因而提供了<include>和<merge>這兩個很是有用的標籤,下面咱們就來逐個學習一下。
java
<include>標籤能夠容許在一個佈局當中引入另一個佈局,那麼好比說咱們程序的全部界面都有一個公共的部分,這個時候最好的作法就是將這個公共的部分提取到一個獨立的佈局文件當中,而後在每一個界面的佈局文件當中來引用這個公共的佈局。android
這裏舉個例子吧,咱們應該都知道,目前幾乎全部的軟件都會有一個頭佈局,頭佈局中能夠包含界面的標題、返回按鈕、以及其它一些操做功能等。那這樣的一個頭佈局,有些軟件是使用ActionBar來實現的,可是因爲ActionBar的靈活性不太好,於是也有不少軟件會選擇本身去編寫實現。那若是本身去實現的話,因爲這個頭佈局是在全部界面都要使用的,顯然咱們不可能在每一個界面當中都去寫一遍這個頭佈局的代碼,所以這種狀況下使用<include>標籤就很是合適了。這裏爲了給你們演示一下,我就編寫一個很是簡單的頭佈局,在res/layout文件夾中新建titlebar.xml做爲頭佈局,代碼以下所示:工具
[html] view plaincopy佈局
<?xml version="1.0" encoding="utf-8"?> 性能
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 學習
android:layout_width="match_parent" 優化
android:layout_height="match_parent" > 編碼
<Button spa
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="Back" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Title"
android:textSize="20sp" />
<Button
android:id="@+id/done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:text="Done" />
</RelativeLayout>
能夠看到,titlebar.xml中的佈局很是簡單,外層是一個RelativeLayout,裏面只有兩個Button和一個TextView,左邊的Button用於實現返回功能,右邊的Button用於實現完成功能,中間的TextView則能夠用於顯示當前界面的標題。咱們能夠來預覽一下titlebar的樣子,以下圖所示:
好的,那titlebar做爲一個獨立的佈局如今咱們已經編寫完了,接下來的工做就很是簡單了,不管任何界面須要加入titlebar這個功能,只須要在佈局文件中引入titlebar.xml就能夠了。那麼好比說咱們的程序當中有一個activity_main.xml文件,如今想要引入titlebar只須要這樣寫:
[html] view plaincopy
<?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" >
<include layout="@layout/titlebar" />
......
</LinearLayout>
很是簡單吧,一行include語句就能夠搞定了。<include>標籤當中能夠指定一個layout屬性,咱們在這個layout屬性中填寫須要引入的佈局名就能夠了。並且使用這種引入的方式,之後若是titlebar的界面有所變動,咱們只須要修改titlebar.xml這一個文件就能夠了,而不是全部界面一個個地去修改。
等等!如今若是你運行一下程序會發現出大問題了,雖然titlebar是成功引入了,可是咱們activity_main.xml中原本的界面所有都不見了!出現這個問題是緣由是由於titlebar的最外層佈局是一個寬高都是match_parent的RelativeLayout,它會將整個佈局都填充滿,於是咱們本來的佈局也就看不見了。那既然問題的緣由清楚了,相信你馬上就想到應該怎麼修改了,將RelativeLayout的layout_height屬性修改爲wrap_content不就能夠了嘛。沒錯,這樣修改固然是沒問題的,不過這種修改方式會讓全部引用titlebar的界面都受到影響,而如何你只但願讓activity_main.xml這一個界面受影響的話,那麼可使用覆寫<include>屬性的方式。
在<include>標籤當中,咱們是能夠覆寫全部layout屬性的,即include中指定的layout屬性將會覆蓋掉titlebar中指定的layout屬性。所以,這裏咱們但願將titlebar的高度設置成wrap_content,就能夠這樣寫:
[html] view plaincopy
<?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" >
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/titlebar" />
......
</LinearLayout>
如今從新運行一下程序應該就能夠一切正常了,以下圖所示:
除了layout_height以外,咱們還能夠覆寫titlebar中的任何一個layout屬性,如layout_gravity、layout_margin等,而非layout屬性則沒法在<include>標籤當中進行覆寫。另外須要注意的是,若是咱們想要在<include>標籤當中覆寫layout屬性,必需要將layout_width和layout_height這兩個屬性也進行覆寫,不然覆寫效果將不會生效。
<merge>標籤是做爲<include>標籤的一種輔助擴展來使用的,它的主要做用是爲了防止在引用佈局文件時產生多餘的佈局嵌套。你們都知道,Android去解析和展現一個佈局是須要消耗時間的,佈局嵌套的越多,那麼解析起來就越耗時,性能也就越差,所以咱們在編寫佈局文件時應該讓嵌套的層數越少越好。
在上面咱們講解<include>標籤的用法時主要介紹了它優勢,可是它也存在着一個很差的地方,就是可能會致使產生多餘的佈局嵌套。這裏仍是經過舉例的方式跟你們說明一下,好比說咱們須要編寫一個肯定取消按鈕的公共佈局,這樣任何一個界面須要肯定和取消功能時就不用再單獨編寫了,新建ok_cancel_layout.xml,代碼以下所示:
[html] view plaincopy
<?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>
能夠看到,這個界面也是很是簡單,外層是一個垂直方向的LinearLayout,LinearLayout中包含了兩個按鈕,一個用於實現肯定功能,一個用於實現取消功能。如今咱們能夠來預覽一下這個界面,以下圖所示:
好的,而後咱們有一個profile.xml的界面須要編輯一些內容,那麼這裏就能夠將ok_cancel_layout這個佈局引入到profile.xml界面當中,以下所示:
[html] view plaincopy
<?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>
在profile.xml當中有一個EditText控件用於編輯內容,而後下面使用了<include>標籤來將ok_cancel_layout佈局進行引入,如今從新運行一下程序,界面效果以下圖所示:
看上去效果很是不錯對嗎?但是在你毫無察覺的狀況下,目前profile.xml這個界面當中其實已經存在着多餘的佈局嵌套了!感受還沒寫幾行代碼呢,怎麼這就已經有多餘的佈局嵌套了?不信的話咱們能夠經過View Hierarchy工具來查看一下,以下圖所示:
能夠看到,最外層首先是一個FrameLayout,這個無可厚非,不知道爲何最外層是FrameLayout的朋友能夠去參考 Android LayoutInflater原理分析,帶你一步步深刻了解View(一) 這篇文章。而後FrameLayout中包含的是一個LinearLayout,這個就是咱們在profile.xml中定義的最外層佈局。接下來的部分就有問題了,在最外層的LinearLayout當中包含了兩個元素,一個是EditText,另外一個又是一個LinearLayout,而後在這個內部的LinearLayout當中才包含了肯定和取消這兩個按鈕。
相信你們已經能夠看出來了吧,這個內部的LinearLayout就是一個多餘的佈局嵌套,實際上並不須要這樣一層,讓兩個按鈕直接包含在外部的LinearLayout當中就能夠了。而這個多餘的佈局嵌套其實就是因爲佈局引入所致使的,由於咱們在ok_cancel_layout.xml中也定義了一個LinearLayout。那麼應該怎樣優化掉這個問題呢?固然就是使用<merge>標籤來完成了,修改ok_cancel_layout.xml中的代碼,以下所示:
[html] view plaincopy
<?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>
能夠看到,這裏咱們將ok_cancel_layout最外層的LinearLayout佈局刪除掉,換用了<merge>標籤,這就表示當有任何一個地方去include這個佈局時,會將<merge>標籤內包含的內容直接填充到include的位置,不會再添加任何額外的佈局結構。好的,<merge>的用法就是這麼簡單,如今從新運行一下程序,你會看到界面沒有任何改變,而後咱們再經過View Hierarchy工具來查看一下當前的View結構,以下圖所示:
OK,能夠看到,如今EditText和兩個按鈕都直接包含在了LinearLayout下面,咱們的profile.xml當中也就不存在多餘的佈局嵌套了。
有的時候咱們會遇到這樣的場景,就是某個佈局當中的元素很是多,但並非全部元素都一塊兒顯示出來的,而是普通狀況下只顯示部分經常使用的元素,而那些不經常使用的元素只有在用戶進行特定操做的狀況下才會顯示出來。
這裏舉個你們都很是熟悉的例子,咱們在添加聯繫人的時候其實能夠編輯的字段真的很是多,姓名、電話、email、傳真、住址、暱稱等等等等,但其實基本上你們最經常使用的就是填一個姓名,填一個電話而已。那麼將這麼多繁雜的字段都一塊兒顯示在界面上其實並非一種很好的作法,由於大多數人都是用不到這些字段的。比較聰明的作法就是把最經常使用的姓名和電話顯示在界面上,而後給用戶提供一個添加更多字段的選項,當用戶真的有須要去添加其它信息的時候,咱們纔將另外的元素顯示到界面上。
說到實現這樣一個功能,我相信大多數人的第一反應就是將不經常使用的元素使用INVISIBLE或者GONE進行隱藏,而後當用戶須要使用這些元素的時候再把它們置成VISIBLE顯示出來。使用這種方式確定能夠實現功能的,可是性能方面就表現得通常了,由於即便是將元素進行隱藏,它們其實仍是在佈局當中的,每一個元素還擁有着本身的寬、高、背景等等屬性,解析佈局的時候也會將這些隱藏的元素一一解析出來。
那麼咱們如何才能讓這些不經常使用的元素僅在須要時纔去加載呢?Android爲此提供了一種很是輕量級的控件,ViewStub。ViewStub雖然說也是View的一種,可是它沒有大小,沒有繪製功能,也不參與佈局,資源消耗很是低,將它放置在佈局當中基本能夠認爲是徹底不會影響性能的。
下面咱們就來學習一下如何使用ViewStub來完成僅在須要時纔去加載佈局的功能,目前profile.xml中只有一個EditText用於編輯信息,那麼好比說咱們還有另外三個不太經常使用的EditText,就能夠將它們定義在另一個佈局文件當中。新建profile_extra.xml文件,代碼以下所示:
[html] view plaincopy
<?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_extra1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:hint="Extra field 1" />
<EditText
android:id="@+id/edit_extra2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint="Extra field 2" />
<EditText
android:id="@+id/edit_extra3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint="Extra field 3" />
</LinearLayout>
能夠看到,在profile_extra.xml這個佈局文件當中定義了三個EditText,也就是用於編輯那些不經常使用信息的控件,如今咱們能夠來預覽一下這個佈局,以下圖所示:
目前profile_extra.xml是一個獨立的佈局,和profile.xml這個佈局文件是徹底沒有關係的。接下來咱們修改profile.xml文件中的代碼,以下所示:
[html] view plaincopy
<?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="@string/edit_something_here" />
<Button
android:id="@+id/more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="20dp"
android:layout_marginBottom="10dp"
android:text="More" />
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/profile_extra"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<include layout="@layout/ok_cancel_layout" />
</LinearLayout>
能夠看到,這裏咱們新增了一個More Button,這個按鈕就是用於去加載那些不經常使用的元素的,而後在Button的下面定義了一個ViewStub。在ViewStub控件中,咱們先是經過id屬性給它指定了一個惟一標識,又經過layout屬性將profile_extra佈局傳入進來,接着給ViewStub指定了一個寬高。注意,雖然ViewStub是不佔用任何空間的,可是每一個佈局都必需要指定layout_width和layout_height屬性,不然運行就會報錯。
接着修改ProfileActivity中的代碼,在Activity中添加More Button的點擊事件,並在點擊事件中進行以下邏輯處理:
[java] view plaincopy
private EditText editExtra1;
private EditText editExtra2;
private EditText editExtra3;
public void onMoreClick() {
ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);
if (viewStub != null) {
View inflatedView = viewStub.inflate();
editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1);
editExtra2 = (EditText) inflatedView.findViewById(R.id.edit_extra2);
editExtra3 = (EditText) inflatedView.findViewById(R.id.edit_extra3);
}
}
當點擊More Button以後咱們首先會調用findViewById()方法將ViewStub的實例獲取到,拿到ViewStub的實例以後就很簡單了,調用inflate()方法或者setVisibility(View.VISIBLE)均可以將隱藏的佈局給加載出來,而加載的這個佈局就是剛纔在XML當中配置的profile_extra佈局。
調用inflate()方法以後會將加載出來的佈局進行返回,以後咱們就能夠對這個佈局進行任意的操做了,再次隱藏顯示,或者獲取子元素的實例等。注意這裏我對ViewStub的實例進行了一個非空判斷,這是由於ViewStub在XML中定義的id只在一開始有效,一旦ViewStub中指定的佈局加載以後,這個id也就失敗了,那麼此時findViewById()獲得的值也會是空。
如今咱們從新運行一下程序,界面以下圖所示:
能夠看到,界面上只有一個More按鈕,ViewStub是徹底不佔用任何空間的。而後點擊一下More按鈕,新的界面以下所示:
沒有問題,profile_extra.xml中定義的佈局已經加載出來了,並且顯示的位置也是在More按鈕和OK按鈕之間,正是ViewStub控件定義的位置,說明咱們確實已經將ViewStub成功使用起來了。
另外須要提醒你們一點,ViewStub所加載的佈局是不可使用<merge>標籤的,所以這有可能致使加載出來的佈局存在着多餘的嵌套結構,具體如何去取捨就要根據各自的實際狀況來決定了,對於那些隱藏的佈局文件結構至關複雜的狀況,使用ViewStub仍是一種至關不錯的選擇的,即便增長了一層無用的佈局結構,仍然仍是利大於弊。