布局優(yōu)化的思想主要就是盡可能地減少布局文件的層級,減少系統(tǒng)繪制的工作量,這也符合扁平化設(shè)計。
使用<include> 標簽
<include> 標簽可以將一個指定的布局文件加載到另一個布局文件中,適合的場景是出現(xiàn)了公共頁面模塊,例如自定義的標題欄。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<include layout="@layout/view_title"/>
<TextView
android:background="#fffff0"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
通過在這里使用 <include> 標簽,@layout/titlebar 指定了引入的布局為 view_title,達到了復用的目的,view_title 布局如下:
<?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="44dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="5dp"
android:text="返回"
android:textSize="15dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:text="標題"
android:textSize="15dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="5dp"
android:text="完成"
android:textSize="15dp" />
</LinearLayout>
<include> 標簽只支持以 android:layout_* 開頭的屬性,例如 layout_width、layout_height,不過指定其他 layout_* 屬性的話,android:layout_width 和 anroid:layout_height 必須存在。不過 android:id 這是一個例外,可以在 <include> 標簽重新指定,而且以這個為準,無論被包含的布局文件的根元素是否指定了 id 屬性。
使用<merge> 標簽
<merge> 標簽一般和 <include> 標簽可以防止在引用布局文件時產(chǎn)生多余的布局嵌套,如下:
<?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/view_merge" />
</LinearLayout>
使用 <merge> 標簽前:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<TextView
android:gravity="center"
android:text="this is merge test."
android:layout_width="match_parent"
android:layout_height="50dp" />
</LinearLayout>
使用 <merge> 標簽后:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<TextView
android:gravity="center"
android:text="this is merge test."
android:layout_width="match_parent"
android:layout_height="50dp" />
</merge>
這樣就可以去掉了那多余的 LinearLayout,同理,當繼承了 RelativeLayout 等ViewGroup 自定義 View 時,如果 xml 頂級標簽和繼承的 ViewGroup 效果相同時,應該把 xml 的頂級標簽替換為 <include> 標簽。例如:
public class MergeView extends LinearLayout {
public MergeView(Context context) {
this(context, null);
}
public MergeView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MergeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(R.layout.view_merge, this, true);
}
}
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:gravity="center"
android:text="this is merge test."
android:layout_width="match_parent"
android:layout_height="50dp" />
</merge>
MergeView 通過 LayoutInflater 加載 view_merge 布局,而 view_merge 布局使用 <merge> 標簽,達到了減少嵌套層級的目的。
使用<ViewStub> 標簽
<ViewStub> 繼承了 View,非常輕量且寬/高都是 0 ,ViewStub 的意義在于按需加載布局文件,在日常開發(fā)中,有很多布局文件在正常情況下不會顯示,有些人可能會把不顯示的布局設(shè)置為 GONE 或者 INVISIBLE,但這樣設(shè)置它們還是加載在布局當中的,每個元素還時擁有著自己的寬、高、背景等等屬性。如果使用 ViewStub 的話可以做到在使用的時候再加載,提高了程序初始化的性能。比如下面的一個示例是網(wǎng)絡出現(xiàn)錯誤時才會加載的頁面:
public class NetworkErrorActivity extends AppCompatActivity {
private TextView mContextTv;
private TextView mErrorTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network_error);
mContextTv = findViewById(R.id.content_tv);
mContextTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mErrorTv == null) {
mErrorTv = (TextView) ((ViewStub) findViewById(R.id.error_stub)).inflate();
}
}
});
}
}
布局activity_network_error:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".layoutoptim.NetworkErrorActivity">
<TextView
android:id="@+id/content_tv"
android:text="正常顯示頁面"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ViewStub
android:id="@+id/error_stub"
android:inflatedId="@+id/error_tv"
android:layout="@layout/view_network_error"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
布局 error_stub:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@android:color/white"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="網(wǎng)絡出錯了">
</TextView>
在 NetworkErrorActivity 的 mContextTv 的點擊事件里去加載了 ViewStub 布局,加載完成后 ViewStub 就會被它內(nèi)部的布局替換掉,這個時候 ViewStub 就不再是布局中的一部分了,這也導致了 inflate() 方法只能被調(diào)用一次,再次調(diào)用則會拋出異常,所以要做好相應的處理。ViewStub 中的 id 即 ViewStub 的 id,inflatedId 則是要被加載布局的根元素的 id。如果在被加載布局的根元素內(nèi)部指定了 id 的同時指定 inflatedId ,以 inflatedId 為主。遺憾的是,ViewStub 目前所加載的布局不支持 <merge> 標簽,這有可能導致加載出來的布局出現(xiàn)多余的嵌套結(jié)構(gòu)。
Double Taxation
通常情況下,系統(tǒng)會在一次遍歷中快速執(zhí)行測量或布局階段。但在一些情況比較復雜的布局中,在最終放置元素之前,系統(tǒng)可能必須對層次結(jié)構(gòu)中需要多次遍歷才能完成最終的效果。必須執(zhí)行不止一次 “測量和布局”的情況稱為 Double Taxation。例如,當使用 RelativeLayout 時,系統(tǒng)會執(zhí)行以下操作:
- 執(zhí)行一次“測量和布局”遍歷,在此過程中,框架會根據(jù)每個子對象的請求計算對象的位置和大小。
- 結(jié)合數(shù)據(jù)和對象的權(quán)重確定關(guān)聯(lián)視圖的恰當位置。
- 執(zhí)行第二次遍歷,以最終確定對象的位置。
- 進入渲染過程的下一階段。
使用 ConstraintLayout 布局
ConstraintLayout 可以很好地解決復雜頁面的嵌套問題。