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

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

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

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

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