本文針對include、merge、ViewStub三個標簽如何在布局復用、有效減少布局層級以及如何可以按需加載三個方面進行介紹的。
復用布局可以幫助我們創建一些可以重復使用的復雜布局。這種方式也意味着應用中任何在多個布局文件之間使用的通用布局都可以被提取出來,然后分別進行管理,使用的時候再進行組合。因此當我們在自定義一些View的時候,使用復用布局會更簡單方便。在平常開發中使用可以復用的布局文件,不僅僅是因為它可以有效減少布局文件數量,更多的目的在於它更方面我們管理應用,布局復用,在更改某個組件時就可以做到改一個布局文件就更改了應用中所有引用該布局文件的組件,做到一改全改。
<include/>
<include/>標簽在布局優化中是使用最多的一個標簽了,它就是為了解決重復定義布局的問題。<include/>標簽就相當於C、C++中的include頭文件一樣,把一些常用的底層的API封裝起來,需要的時候引入即可。在一些開源的J2EE中許多XML配置文件也都會使用<include/>標簽,將多個配置文件組合成為一個更為復雜的配置文件,如最常見的S2SH。
在以前Android開發中,由於ActionBar設計上的不統一以及兼容性問題,所以很多應用都自定義了一套自己的標題欄titlebar。標題欄我們知道在應用的每個界面幾乎都會用到,在這里可以作為一個很好的示例來解釋<include/>標簽的使用。
下面是一個自定義的titlebar文件:
1
2
3
4
5
6
7
8
9
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/titlebar_bg">
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/gafricalogo" />
</FrameLayout>
|
在應用中使用titlebar布局文件,我們通過<include/>標簽,布局文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/app_bg"
android:gravity="center_horizontal">
<include layout="@layout/titlebar"/>
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:padding="10dp" />
...
</LinearLayout>
|
在<include/>標簽中可以覆蓋導入的布局文件root布局的布局屬性(如layout_*屬性)。
布局示例如下:
1
2
3
4
|
<include android:id="@+id/news_title"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="@layout/title"/>
|
如果想使用<include/>標簽覆蓋嵌入布局root布局屬性,必須同時覆蓋layout_height和layout_width屬性,否則會直接報編譯時語法錯誤。
Layout parameter layout_height ignored unless layout_width is also specified on <include> tag
如果<include/>標簽已經定義了id,而嵌入布局文件的root布局文件也定義了id,<include>標簽的id會覆蓋掉嵌入布局文件root的id,如果include標簽沒有定義id則會使用嵌入文件root的id。
<merge/>
<merge/>標簽都是與<include/>標簽組合使用的,它的作用就是可以有效減少View樹的層次來優化布局。
下面通過一個簡單的示例探討一下<merge/>標簽的使用,下面是嵌套布局的layout_text.xml文件:
1
2
3
4
5
6
7
8
9
10
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:text="Hello World!"
android:layout_height="match_parent" />
</LinearLayout>
|
一個線性布局中嵌套一個文本視圖,主布局如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_wrap"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<include
android:id="@+id/layout_import"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="@layout/layout_text" />
</LinearLayout>
|
通過hierarchyviewer我們可以看到主布局View樹的部分層級結構如下圖:
現在講嵌套布局跟布局標簽更改為<merge/>,merge_text.xml布局文件如下:
1
2
3
4
5
6
7
8
9
|
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello World!"/>
</merge>
|
然后將主布局<include/>標簽中的layout更改為merge_text.xml,運行后重新截圖如下:
對比截圖就可以發現上面的四層結構,現在已經是三層結構了。當我們使用<merge/>標簽的時候,系統會自動忽略merge層級,而把TextView直接放置與<include/>平級。
<merge/>標簽在使用的時候需要特別注意布局的類型,例如我的<merge/>標簽中包含的是一個LinearLayout布局視圖,布局中的元素是線性排列的,如果嵌套進主布局時,include標簽父布局時FrameLayout,這種方式嵌套肯定會出問題的,merge中元素會按照FrameLayout布局方式顯示。所以在使用的時候,<merge/>標簽雖然可以減少布局層級,但是它的限制也不可小覷。
<merge/>只能作為XML布局的根標簽使用。當Inflate以<merge/>開頭的布局文件時,必須指定一個父ViewGroup,並且必須設定attachToRoot為true。
View android.view.LayoutInflater.inflate(int resource, ViewGroup root, boolean attachToRoot)
root不可少,attachToRoot必須為true。
ViewStub
在開發過程中,經常會遇到這樣一種情況,有些布局很復雜但是卻很少使用。例如條目詳情、進度條標識或者未讀消息等,這些情況如果在一開始初始化,雖然設置可見性View.GONE
,但是在Inflate的時候View仍然會被Inflate,仍然會創建對象,由於這些布局又想到復雜,所以會很消耗系統資源。
ViewStub就是為了解決上面問題的,ViewStub是一個輕量級的View,它一個看不見的,不占布局位置,占用資源非常小的控件。
定義ViewStub布局文件
下面是一個ViewStub布局文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_wrap"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ViewStub
android:id="@+id/stub_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/image_import"
android:layout="@layout/layout_image" />
<ViewStub
android:id="@+id/stub_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/text_import"
android:layout="@layout/layout_text" />
</LinearLayout>
|
layout_image.xml文件如下(layout_text.xml類似):
1
2
3
4
5
6
7
8
9
10
11
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/layout_image">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
|
加載ViewStub布局文件
動態加載ViewStub所包含的布局文件有兩種方式,方式一使用使用inflate()方法,方式二就是使用setVisibility(View.VISIBLE)。
示例java代碼如下:
1
2
3
4
5
6
7
8
9
10
11
|
private ViewStub viewStub;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main2);
viewStub = (ViewStub) findViewById(R.id.stub_image);
//viewStub.inflate();//方式一
viewStub.setVisibility(View.VISIBLE);//方式二
ImageView imageView = (ImageView) findViewById(R.id.imageView);
imageView.setImageResource(R.drawable.image);
}
|
示例View層級截圖如下:
ViewStub一旦visible/inflated,它自己就不在是View試圖層級的一部分了。所以后面無法再使用ViewStub來控制布局,填充布局root布局如果有id,則會默認被
android:inflatedId所設置的id取代,如果沒有設置android:inflatedId,則會直接使用填充布局id。
由於ViewStub這種使用后即可就置空的策略,所以當需要在運行時不止一次的顯示和隱藏某個布局,那么ViewStub是做不到的。這時就只能使用View的可見性來控制了。
layout_*相關屬性與include標簽相似,如果使用應該在ViewStub上面使用,否則使用在嵌套進來布局root上面無效。
ViewStub的另一個缺點就是目前還不支持merge標簽。
小結
Android布局優化基本上就設計上面include、merge、ViewStub三個標簽的使用。在平常開發中布局推薦使用RelativeLayout,它也可以有效減少布局層級嵌套。最后了將merge和include源碼附上,ViewStub就是一個View,就不貼出來了。
Include源碼
1
2
3
4
5
6
7
8
9
10
|
/**
* Exercise <include /> tag in XML files.
*/
public class Include extends Activity {
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.include_tag);
}
}
|
Merge源碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/**
* 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;
}
}
|