多層布局的嵌套會導致頁面加載慢,影響用戶的體驗,今天我們就來學學如何使用 include,merge及viewStub。
1.include
include便于對相同視圖內容進行統一的控制管理,提高布局重用性,以標題欄為例,我們先定義一個通用的標題欄,相關代碼如下:
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="標題"
android:textSize="18sp" />
</RelativeLayout>
然后在我們的MainActivity頁面引入,我們的MainActivity頁面有一個加載視圖的按鈕
<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>
效果如下:
那我們如果想設置標題怎么辦?當然是findviewbyid()然后set了,如下:
RelativeLayout relativeLayout = findViewById(R.id.ll_commontitle_root);
TextView titleTv = relativeLayout.findViewById(R.id.tv_title_commontitle);
titleTv.setText("主界面");
運行,我擦,報錯了。
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)
不能找到這個id,獲取的RelativeLayout對象為null,這是為何?原來:如果給include設置了id,就會覆蓋掉引用布局根布局的id,所以解決辦法用兩種:
- 第一種直接獲取include的id,進行findviewByid()
- 第二種將兩者的id取名一致
我們選取第一種,結果如下:
2.merge
merge標簽是作為include標簽的一種輔助擴展來使用的,也就是需要和include一起使用,它的主要作用是為了防止在引用布局文件時產生多余的布局嵌套。我們先看看我們現在的視圖層級(通過android studio自帶的Layout inspector):
歐克,我們看看我們將include中的布局改為merge,注意:merge必須放在布局文件的根節點上。這里做一個說明如果將RelativeLayout改為merge,Releative中所有的屬性將都無法使用,因為merge不是一個view,merge extends Activity,所以我們直接刪除相關屬性:
<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="標題"
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中(第一種寫法):
TextView titleTv = findViewById(R.id.tv_title_commontitle);
titleTv.setText("主界面");
其實還有一種寫法是不在xml中通過include引入,而是通過代碼直接引入merge:
我們給activity_main.xml的根Relative設置id為 android:id="@+id/rl_main_root",在通過LayoutInflate.inflate方法渲染的時候, 第二個參數必須指定一個父容器,且第三個參數必須為true,也就是必須為merge下的視圖指定一個父親節
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("主界面");
結果:
運行后再查看一下視圖層級:
merge的使用,相當于直接將原RelativeLayout中的控件搬運到了父RelativeLayout中,所以merge所包含的控件之前的位置屬性啥的要做響應的調整,對于父RelativeLayout。
2.1merge的優缺點
通過上面的代碼及效果我們可以明顯的看的優缺點。
2.1.1merge的優點
- 減少了層級的嵌套,提高了渲染的效率。
2.1.2merge的缺點
缺點也是比較明顯:
- 由于merge不是view.原ViewGroup的屬性都失效(對merge標簽設置的所有屬性都是無效的),也就是背景色啥的都不能正常顯示。
3.ViewStub
ViewStub有點類似于懶加載,就是什么時候需要加載相關視圖了,在做顯示。話不多說,上代碼:
<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是要加載的布局。其余屬性不多說。
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中的內容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中的內容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中的內容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) {
// 這里調用的是inflate方法,當然,也可以調用setVisibility方法(但是不建議這么做)
// 只能點擊一次加載視圖按鈕,因為inflate只能被調用一次。調用完成ViewStub被銷毀
// 如果再次點擊按鈕,會拋出異常"ViewStub must have a non-null ViewGroup viewParent"
ViewStub viewStub = findViewById(R.id.vs_main);
if(viewStub != null){
viewStub.inflate();
//這里注意ViewStub只是一個容器,所以在其顯示后,其中的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,"點擊了第一個ViewStub按鈕",Toast.LENGTH_LONG).show();
}
});
btnTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"點擊了第二個ViewStub按鈕",Toast.LENGTH_LONG).show();
}
});
}
}
});
我們獲取了ViewStub內容沒有加載的布局層級:
ViewStub內容已加載的布局層級:
ViewStub標簽使用注意點:
1,ViewStub標簽不支持merge標簽(ViewStub的加載布局中不能有merge,但merge中可以有ViewStub)。因此這有可能導致加載出來的布局存在著多余的嵌套結構,開發中視情況而定。
2,ViewStub的inflate只能被調用一次,第二次調用會拋出異常。
3,雖然ViewStub是不占用任何空間的,但是每個布局都必須要指定layout_width和layout_height屬性,否則運行就會報錯。
完畢!