多層布局的嵌套會(huì)導(dǎo)致頁(yè)面加載慢,影響用戶(hù)的體驗(yàn),今天我們就來(lái)學(xué)學(xué)如何使用 include,merge及viewStub。
1.include
include便于對(duì)相同視圖內(nèi)容進(jìn)行統(tǒng)一的控制管理,提高布局重用性,以標(biāo)題欄為例,我們先定義一個(gè)通用的標(biāo)題欄,相關(guān)代碼如下:
commont_title
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_commontitle_root"
android:layout_width="match_parent"
android:layout_height="70dp"
android:background="#0951C1">
<TextView
android:id="@+id/tv_back_commontitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:drawableLeft="@mipmap/back"
android:drawablePadding="3dp"
android:gravity="center_vertical"
android:layout_centerVertical="true"
android:minHeight="50dp"
android:text="返回"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_title_commontitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="標(biāo)題"
android:textSize="18sp" />
</RelativeLayout>
然后在我們的MainActivity頁(yè)面引入,我們的MainActivity頁(yè)面有一個(gè)加載視圖的按鈕
<RelativeLayout
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"
tools:context=".MainActivity">
<include
android:id="@+id/iclude_main"
layout="@layout/common_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<Button
android:id="@+id/btn_main_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="加載視圖"/>
</RelativeLayout>
效果如下:

那我們?nèi)绻朐O(shè)置標(biāo)題怎么辦?當(dāng)然是findviewbyid()然后set了,如下:
RelativeLayout relativeLayout = findViewById(R.id.ll_commontitle_root);
TextView titleTv = relativeLayout.findViewById(R.id.tv_title_commontitle);
titleTv.setText("主界面");
運(yùn)行,我擦,報(bào)錯(cuò)了。
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.widget.RelativeLayout.findViewById(int)' on a null object reference
at com.hxzk.layoutoptimizeproject.MainActivity.onCreate(MainActivity.java:17)
不能找到這個(gè)id,獲取的RelativeLayout對(duì)象為null,這是為何?原來(lái):如果給include設(shè)置了id,就會(huì)覆蓋掉引用布局根布局的id,所以解決辦法用兩種:
- 第一種直接獲取include的id,進(jìn)行findviewByid()
- 第二種將兩者的id取名一致
我們選取第一種,結(jié)果如下:

2.merge
merge標(biāo)簽是作為include標(biāo)簽的一種輔助擴(kuò)展來(lái)使用的,也就是需要和include一起使用,它的主要作用是為了防止在引用布局文件時(shí)產(chǎn)生多余的布局嵌套。我們先看看我們現(xiàn)在的視圖層級(jí)(通過(guò)android studio自帶的Layout inspector):

歐克,我們看看我們將include中的布局改為merge,注意:merge必須放在布局文件的根節(jié)點(diǎn)上。這里做一個(gè)說(shuō)明如果將RelativeLayout改為merge,Releative中所有的屬性將都無(wú)法使用,因?yàn)閙erge不是一個(gè)view,merge extends Activity,所以我們直接刪除相關(guān)屬性:
<merge
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/tv_back_commontitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:drawableLeft="@mipmap/back"
android:gravity="center"
android:text="返回"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_title_commontitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="標(biāo)題"
android:textSize="18sp" />
</merge>
activity_main.xml中:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rl_main_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<include
layout="@layout/common_title"
/>
<Button
android:id="@+id/btn_main_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="加載視圖"/>
</RelativeLayout>
MainActivity中(第一種寫(xiě)法):
TextView titleTv = findViewById(R.id.tv_title_commontitle);
titleTv.setText("主界面");
其實(shí)還有一種寫(xiě)法是不在xml中通過(guò)include引入,而是通過(guò)代碼直接引入merge:
我們給activity_main.xml的根Relative設(shè)置id為 android:id="@+id/rl_main_root",在通過(guò)LayoutInflate.inflate方法渲染的時(shí)候, 第二個(gè)參數(shù)必須指定一個(gè)父容器,且第三個(gè)參數(shù)必須為true,也就是必須為merge下的視圖指定一個(gè)父親節(jié)
RelativeLayout parentRl = findViewById(R.id.rl_main_root);
LayoutInflater.from(this).inflate(R.layout.common_title,parentRl,true);
TextView titleTv = findViewById(R.id.tv_title_commontitle);
titleTv.setText("主界面");
結(jié)果:

運(yùn)行后再查看一下視圖層級(jí):

merge的使用,相當(dāng)于直接將原RelativeLayout中的控件搬運(yùn)到了父RelativeLayout中,所以merge所包含的控件之前的位置屬性啥的要做響應(yīng)的調(diào)整,對(duì)于父RelativeLayout。
2.1merge的優(yōu)缺點(diǎn)
通過(guò)上面的代碼及效果我們可以明顯的看的優(yōu)缺點(diǎn)。
2.1.1merge的優(yōu)點(diǎn)
- 減少了層級(jí)的嵌套,提高了渲染的效率。
2.1.2merge的缺點(diǎn)
缺點(diǎn)也是比較明顯:
- 由于merge不是view.原ViewGroup的屬性都失效(對(duì)merge標(biāo)簽設(shè)置的所有屬性都是無(wú)效的),也就是背景色啥的都不能正常顯示。
3.ViewStub
ViewStub有點(diǎn)類(lèi)似于懶加載,就是什么時(shí)候需要加載相關(guān)視圖了,在做顯示。話不多說(shuō),上代碼:
<Button
android:id="@+id/btn_main_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="加載視圖"/>
<ViewStub
android:id="@+id/vs_main"
android:inflatedId="@+id/vs_main_inflatedId"
android:layout="@layout/vs_layout"
android:layout_below="@id/btn_main_next"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
其中inflatedId是要加載的layout根布局的ViewGroup的id,layout是要加載的布局。其余屬性不多說(shuō)。
vs_layout布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/vs_main_inflatedId"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_vscontent_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="viewStub中的內(nèi)容0"
android:layout_gravity="center"
/>
<Button
android:id="@+id/btn_vscontent_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="viewStub中的內(nèi)容1"
android:layout_gravity="center"
/>
<Button
android:id="@+id/btn_vscontent_three"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="viewStub中的內(nèi)容2"
android:layout_gravity="center"
/>
</LinearLayout>
MainActivity中的代碼:
Button btnNext =findViewById(R.id.btn_main_next);
btnNext.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 這里調(diào)用的是inflate方法,當(dāng)然,也可以調(diào)用setVisibility方法(但是不建議這么做)
// 只能點(diǎn)擊一次加載視圖按鈕,因?yàn)閕nflate只能被調(diào)用一次。調(diào)用完成ViewStub被銷(xiāo)毀
// 如果再次點(diǎn)擊按鈕,會(huì)拋出異常"ViewStub must have a non-null ViewGroup viewParent"
ViewStub viewStub = findViewById(R.id.vs_main);
if(viewStub != null){
viewStub.inflate();
//這里注意ViewStub只是一個(gè)容器,所以在其顯示后,其中的view就是在Activity中展示,所以直接findViewByid即可
Button btnOne =findViewById(R.id.btn_vscontent_one);
Button btnTwo =findViewById(R.id.btn_vscontent_two);
btnOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"點(diǎn)擊了第一個(gè)ViewStub按鈕",Toast.LENGTH_LONG).show();
}
});
btnTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"點(diǎn)擊了第二個(gè)ViewStub按鈕",Toast.LENGTH_LONG).show();
}
});
}
}
});
我們獲取了ViewStub內(nèi)容沒(méi)有加載的布局層級(jí):

ViewStub內(nèi)容已加載的布局層級(jí):

ViewStub標(biāo)簽使用注意點(diǎn):
1,ViewStub標(biāo)簽不支持merge標(biāo)簽(ViewStub的加載布局中不能有merge,但merge中可以有ViewStub)。因此這有可能導(dǎo)致加載出來(lái)的布局存在著多余的嵌套結(jié)構(gòu),開(kāi)發(fā)中視情況而定。
2,ViewStub的inflate只能被調(diào)用一次,第二次調(diào)用會(huì)拋出異常。
3,雖然ViewStub是不占用任何空間的,但是每個(gè)布局都必須要指定layout_width和layout_height屬性,否則運(yùn)行就會(huì)報(bào)錯(cuò)。
完畢!