Android佈局優化利器include和ViewStub

孫國威的部落格發表於2014-09-13

當建立複雜的佈局的時候,有時候會發現新增了很多的ViewGroup和View。隨之而來的問題是View樹的層次越來越深,應用也變的越來越慢,因為UI渲染是非常耗時的。

這時候就應該進行佈局優化了。這裡介紹兩種方式,分別為<include>標籤和ViewStub類。

<include/>

使用<include/>是為了避免程式碼的重複。設想一種情況,我們需要為app中的每個檢視都新增一個footer,這個footer是一個顯示app名字的TextView。通常多個Activity對應多個XML佈局檔案,難道要把這個TextView複製到每個XML中嗎?如果TextView需要做修改,那麼每個XML佈局檔案都要進行修改,那簡直是噩夢。

物件導向程式設計的其中一個思想就是程式碼的複用,那麼怎麼進行佈局的複用呢?這時,<include/>就起作用了。

如果學過C語言,那麼對#include應該不陌生,它是一個預編譯指令,在程式編譯成二進位制檔案之前,會把#include的內容拷貝到#include的位置。

Android中的<include/>也可以這麼理解,就是把某些通用的xml程式碼拷貝到<include/>所在的地方。以一個Activity為例。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center_horizontal"
        android:text="@string/hello" />

    <include layout="@layout/footer_with_layout_properties"/>

</RelativeLayout>

footer_with_layout_properties.xml中就是一個簡單的TextView,程式碼如下:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_marginBottom="30dp"
    android:gravity="center_horizontal"
    android:text="@string/footer_text" />

上述的程式碼中,我們使用了<include/>標籤,達到了程式碼複用的目的。

但是,仍然存在一些疑惑。

footer_with_layout_properties.xml中使用了android:layout_alignParentBottom屬性,這個屬性之所以可行,是因為外層佈局是RelativeLayout。

那麼,如果外層佈局換做LinearLayout又會怎樣呢?答案顯而易見,這肯定是行不通的。那麼怎麼辦呢?我們可以把具體的屬性寫在<include/>標籤裡面,看下面的程式碼。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center_horizontal"
        android:text="@string/hello"/>
    <include
        layout="@layout/footer"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="30dp"/>
</RelativeLayout>

我們直接在<include/>標籤裡面使用了android:layout_*屬性。

注意:如果想要在<include/>標籤中覆蓋被包含佈局所指定的任何android:layout_*屬性,必須在<include/>標籤中同時指定layout_width和layout_height屬性,這可能是一個Android系統的一個bug吧。

ViewStub

在開發過程中,難免會遇到各種互動問題,例如顯示或隱藏某個檢視。如果想要一個檢視只在需要的時候顯示,可以嘗試使用ViewStub這個類。

先看一下ViewStub的官方介紹:

“ViewStub是一個不可視並且大小為0的檢視,可以延遲到執行時填充佈局資源。當ViewStub設定為Visible或呼叫inflate()之後,就會填充佈局資源,ViewStub便會被填充的檢視替代”。

現在已經清楚ViewStub能幹什麼了,那麼看一個例子。一個佈局中,存在一個MapView,只有需要它的時候,才讓它顯示出來。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:onClick="onShowMap"
        android:text="@string/show_map" />

    <ViewStub
        android:id="@+id/map_stub"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:inflatedId="@+id/map_view"
        android:layout="@layout/map" />

</RelativeLayout>

map.xml檔案中包含一個MapView,只有在必要的時候,才會讓它顯示出來。

<com.google.android.maps.MapView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/map_view"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:apiKey="my_api_key"
    android:clickable="true" />

另外,inflatedId是ViewStub被設定成Visible或呼叫inflate()方法後返回的id,這個id就是被填充的View的id。在這個例子中,就是MapView的id。

接下來看看ViewStub是怎麼使用的。

public class MainActivity extends MapActivity {

  private View mViewStub;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    mViewStub = findViewById(R.id.map_stub);
  }

  public void onShowMap(View v) {
    mViewStub.setVisibility(View.VISIBLE);
  }

  @Override
  protected boolean isRouteDisplayed() {
    return false;
  }
}

題外話

有的同學肯定會問,使用ViewStub和單純地把View設定為View.GONE或View.VISIBLE有什麼區別呢?不都是顯示和隱藏嗎,使用ViewStub反而更麻煩了。

確實是有區別的,會涉及到View樹的渲染,記憶體消耗等。

至於有什麼具體的差別,就請大家自己去Google吧。俗話說,自己動手,豐衣足食嘛!

參考資料

http://code.google.com/p/android/issues/detail?id=2863

http://android-developers.blogspot.com.ar/2009/03/android-layout-tricks-3-optimize-with.html

http://developer.android.com/reference/android/view/ViewStub.html

相關文章