在app開(kāi)發(fā)中布局幾乎是必不可少的基礎(chǔ),隨著UI越來(lái)越多,布局的重復(fù)性、復(fù)雜度也會(huì)隨之增長(zhǎng)。而每一個(gè)view都需要measure ,layout ,draw這三步來(lái)完成,如果層級(jí)太深自然會(huì)導(dǎo)致整個(gè)繪制過(guò)程耗時(shí)大量的時(shí)間,從而造成啟動(dòng)速度慢,卡頓都問(wèn)題。而ondraw在頻繁刷新時(shí)可能多次觸發(fā),因此他又不能做耗時(shí)的操作,還可能內(nèi)存抖動(dòng)。對(duì)于布局檢查我們主要使用Layout Inspector.
Layout Inspector布局分析工具
Android Studio IDE的菜單Tools--->Layout Inspector子菜單,如下圖:

啟動(dòng)模擬器或者真機(jī),debug運(yùn)行app至設(shè)備,選擇Layout Inspector 后,會(huì)出現(xiàn)下面的窗口:

Component Tree窗口:查看當(dāng)前視圖的全部view樹(shù);控制視圖的顯示與隱藏(API 29-30的設(shè)備),如下圖:

? 中間窗口:選擇應(yīng)用、刷新頁(yè)面視圖、呈現(xiàn)分頁(yè)的頁(yè)面效果圖、Live updates實(shí)時(shí)更新手機(jī)畫面;
? Attributes窗口:選擇Component Tree窗口中的一個(gè)子View視圖時(shí),呈現(xiàn)該子視圖的全部配置屬性。
總結(jié):
Layout Inspector 只能用于查看布局view樹(shù)層級(jí),不能查看布局性能;并且對(duì)設(shè)備的api的要求比較高(API29-30)
從上面分析的布局層級(jí)中,Google給我們提供了三個(gè)優(yōu)化布局的方案,下面就逐一介紹一下:
include標(biāo)簽(簡(jiǎn)化布局結(jié)果,提供復(fù)用性)
?
? app開(kāi)發(fā)過(guò)程中,會(huì)遇到不同的Activity里面有相同的布局,這時(shí)我們可以將這些通用的布局提取出來(lái)到一個(gè)單獨(dú)的layout文件里,再使用include標(biāo)簽引入到相應(yīng)的頁(yè)面布局文件里。
使用場(chǎng)景:
? 一個(gè)APP的頂部布局、側(cè)邊欄布局、底部Tab欄布局、ListView和GridView每一項(xiàng)的布局等
特別說(shuō)明:
- 如果我們需要給include標(biāo)簽和其所在加載的布局根節(jié)點(diǎn)設(shè)置id的話,這兩個(gè)id必須一致,或者只給其中一個(gè)設(shè)置id即可;
- include 引用的布局文件的id不要和外面主布局中的id設(shè)置相同了,否則會(huì)出現(xiàn)找錯(cuò)視圖;
- 如果需要給include設(shè)置位置時(shí),那么必須要設(shè)置大小width ,height,否則編譯會(huì)出問(wèn)題,一般情況不需要設(shè)置;
- 如果一個(gè)布局文件中引入兩個(gè)相同的include布局,那么就需要給include設(shè)置不同的id來(lái)進(jìn)行區(qū)分;
- 當(dāng)你使用了databinding時(shí),就必須要給include標(biāo)簽制定id
使用方法:
<include layout="@layout/custom_titlebar_layout" />
Merge標(biāo)簽(減少布局層次)
? merger標(biāo)簽相當(dāng)于一個(gè)包裹的作用,他在優(yōu)化UI接口的時(shí)候主要目的是通過(guò)刪減多余或額外的層級(jí),從而優(yōu)化整個(gè)android layout的結(jié)果。
使用場(chǎng)景(典型)
1.布局頂節(jié)點(diǎn)是FrameLayout且不需要設(shè)置background或Padding等屬性時(shí),可以用merge標(biāo)簽來(lái)替代,因?yàn)锳ctivity內(nèi)容視圖的parent view就是FrameLayout
2.當(dāng)某一個(gè)布局作為子布局include時(shí),使用merge當(dāng)作該子布局的頂節(jié)點(diǎn),這樣在被引入的時(shí)候回自動(dòng)被忽略掉,而將其子節(jié)點(diǎn)布局全部合并到主布局中
3.自定義一個(gè)組合視圖時(shí)(自定義多個(gè)系統(tǒng)widget組合的視圖,自定義視圖繼承于Linearlayout ,RelativeLayout等)
特別說(shuō)明:
只能作為根布局使用
當(dāng)Infalte以merge標(biāo)簽開(kāi)始的布局文件時(shí),必須制定一個(gè)父viewGroup ,并且必須要設(shè)定attachToRoot為true
RelativeLayout mRootLayout = (RelativeLayout) mLayoutInflater .inflate(R.layout.customview_titlebar_layout, this,true);
簡(jiǎn)單使用:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:clickable="true"
android:src="@drawable/vector"
tools:ignore="MissingConstraints"
app:tint="@color/selector" />
</merge>
由于activity 在加載布局setContent時(shí),是加載到ContentFrameLayout中去,而我們從源碼可以看到ContentFrameLayout是繼承自FrameLayout的:
public class ContentFrameLayout extends FrameLayout {
public interface OnAttachListener {
void onDetachedFromWindow();
void onAttachedFromWindow();
}
//............
}
所以我們可以直接使用merge標(biāo)簽來(lái)作為布局的根節(jié)點(diǎn),我們?cè)俅问褂肔ayout Inspector分析工具來(lái)分析,發(fā)現(xiàn)并沒(méi)有merge標(biāo)簽這一層級(jí),如下:

ViewStub標(biāo)簽(需要時(shí)加載)
他是在需要的時(shí)候去加載,不需要時(shí)則不用加載,這樣可以節(jié)約內(nèi)存使用。我們通常使用的visiable ,gone ,invisiable來(lái)控制顯示隱藏,其實(shí)這些視圖時(shí)已經(jīng)加載好了的。
ViewStub 是一個(gè)輕量級(jí)的View,它一個(gè)看不見(jiàn)的,不占布局位置,占用資源非常小的控件。他是一個(gè)寬高都為0的view ,默認(rèn)是不可見(jiàn)的。只有通過(guò)調(diào)用setVisibility函數(shù)或者Inflate函數(shù)才會(huì)將其要裝載的目標(biāo)布局給加載出來(lái),從而達(dá)到延遲加載的效果,這個(gè)要被加載的布局通過(guò)android:layout屬性來(lái)設(shè)置。
使用場(chǎng)景
- 在程序的運(yùn)行期間,某個(gè)布局在Inflate后,就不會(huì)有變化,除非重新啟動(dòng)。
- 想要 單次 控制顯示與隱藏的是一個(gè)布局文件,而非某個(gè)View。
特別說(shuō)明:
ViewStub只能Inflate一次,之后ViewStub對(duì)象會(huì)被置為空。按句話說(shuō),某個(gè)被ViewStub指定的布局被Inflate后,就不會(huì)夠再通過(guò)ViewStub來(lái)控制它了。
ViewStub只能用來(lái)Inflate一個(gè)布局文件,而不是某個(gè)具體的View,當(dāng)然也可以把View寫在某個(gè)布局文件中。
簡(jiǎn)單使用:
<!--自定義個(gè)布局-->
<?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">
<TextView
android:id="@+id/introduce"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/colorPrimary"
android:textSize="18sp"
android:text="I am a subview of viewstub "/>
</LinearLayout>
<ViewStub
android:id="@+id/viewstub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/viewstub_layout" />
viewStub.inflate().findViewById(R.id.introduce);//在調(diào)用inflate()時(shí),view就已經(jīng)加載好了
個(gè)人覺(jué)得在列表頁(yè)面使用時(shí)比較方便,例如在頁(yè)面加載時(shí),不需要加載RecycleView,待網(wǎng)絡(luò)請(qǐng)求響應(yīng)后,再加載列表,這樣可以使頁(yè)面加載速度加快。
扁平化布局constraintLayout
盡量使用constraintLayout約束布局來(lái)使布局盡量扁平化,減少非必需的UI組件。
關(guān)于ConstraintLayout的使用將在新的文章中介紹。
溫馨提示:上述三個(gè)布局的使用都有一些限制,在使用時(shí)一定一定要多加注意。