導(dǎo)言
這節(jié)主要是講一下布局方面關(guān)于UI的優(yōu)化手段燃领,屬于編碼中的一些細(xì)節(jié)處理
UI流暢性優(yōu)化
先看Systrace中的某一幀
從Alert提示中我們也可以知道反饋的是測量和布局時間過久猛蔽,所以說后續(xù)要做的就是優(yōu)化測量和布局的時間
ViewStub
先看一下ViewStub里面的一些關(guān)鍵代碼
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewStub, defStyleAttr, defStyleRes);
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
a.recycle();
//可以看到颠焦,默認(rèn)是不顯示的
setVisibility(GONE);
setWillNotDraw(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//默認(rèn)的大小也是0朴上,并且不會有任何測量操作
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
//本身并不會繪制任何東西
}
@Override
protected void dispatchDraw(Canvas canvas) {
//同樣的也不會要求別人繪制任何東西
}
從上面我們可以看出,實際上ViewStub就是一個占位容器,本身不會做任何操作她按,并且測量的時候默認(rèn)為GONE的視圖并不會參與測量
再看setVisibility方法
@Override
@android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
//如果內(nèi)部布局已經(jīng)inflate過,那么直接顯示或者隱藏
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
//否則顯示自己比勉,然后再進(jìn)行inflate操作
super.setVisibility(visibility);
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final View view = inflateViewNoAdd(parent);
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
private View inflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
//這里才進(jìn)行ViewStub內(nèi)的布局的inflate操作
final View view = factory.inflate(mLayoutResource, parent, false);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
return view;
}
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
//將當(dāng)前ViewStub從父布局中移除
parent.removeViewInLayout(this);
//然后將ViewStub內(nèi)的布局直接添加到父布局中
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}
看完之后我們就清楚了清钥,相比于直接把視圖放入xml中,ViewStub一開始并不會直接進(jìn)行inflate操作后众,而是等到手動調(diào)用inflate或者setVisibility(View.VISIBLE)距帅,也就是說在ViewStub中的layout的inflate被延遲處理了
結(jié)論:ViewStub的使用場景就是一個視圖需要滿足一定條件的情況下才顯示,此時該視圖就應(yīng)該通過ViewStub修飾,通過延遲加載的方式來優(yōu)化原始原來布局的測量等速度恩敌,因為這個布局不一定可見
布局優(yōu)化
我們知道Android的測量是從頂層布局開始然后一直向下分發(fā)恢口,所謂布局優(yōu)化,實際上就是降低inflate和測量/布局的耗時,比方說對比于RelativeLayout和LinearLayout,先看一個布局
<?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"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text1"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text2"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text3"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text4"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text5"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text6"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text7"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text8"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text9"
/>
</LinearLayout>
</LinearLayout>
然后看一下直接使用RelativeLayout的效果
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text1"
/>
<TextView
android:id="@+id/tv_text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text2"
android:layout_toRightOf="@+id/tv_text1"
/>
<TextView
android:id="@+id/tv_text3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text3"
android:layout_toRightOf="@+id/tv_text2"
/>
<TextView
android:id="@+id/tv_text4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text4"
android:layout_below="@+id/tv_text1"
/>
<TextView
android:id="@+id/tv_text5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text5"
android:layout_toRightOf="@+id/tv_text4"
android:layout_below="@+id/tv_text1"
/>
<TextView
android:id="@+id/tv_text6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text6"
android:layout_toRightOf="@+id/tv_text5"
android:layout_below="@+id/tv_text1"
/>
<TextView
android:id="@+id/tv_text7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text7"
android:layout_below="@+id/tv_text4"
/>
<TextView
android:id="@+id/tv_text8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text8"
android:layout_toRightOf="@+id/tv_text7"
android:layout_below="@+id/tv_text4"
/>
<TextView
android:id="@+id/tv_text9"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text9"
android:layout_toRightOf="@+id/tv_text8"
android:layout_below="@+id/tv_text4"
/>
</RelativeLayout>
大致分析一下差別,其實可以看出蜜另,如果在同樣的實現(xiàn)效果下鹅很,使用RelativeLayout可以減少視圖的個數(shù)整袁,從而優(yōu)化inflate的效率绳匀,相對于測量和布局來說痹仙,這個比較明顯
當(dāng)然例子中的布局層級比較簡單,所以說沒有看出measure/layout上面的差別,當(dāng)布局特別復(fù)雜的時候脚乡,這個也是要考慮的部分
結(jié)論:
優(yōu)先應(yīng)該考慮降低布局的層級窒典,實際上也就是降低視圖的數(shù)量,這樣可以相對有效的降低inflate的耗時
merge
上面提到了要降低布局層級,實際開發(fā)中會出現(xiàn)一種情況,比方說setContentView浇冰,實際上父布局就是一個FrameLayout漂佩,如果此時我們的xml中父布局是FrameLayout
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_image2"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@mipmap/ic_launcher"
android:layout_gravity="center"
/>
<ImageView
android:id="@+id/iv_image3"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@mipmap/ic_launcher"
android:layout_gravity="right"
/>
</FrameLayout>
此時想到于FrameLayout中嵌套FrameLayout,很明顯這個是多余咳榜,所幸的是Android提供了方式來處理氯夷,可以通過merge標(biāo)簽來合并
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:id="@+id/iv_image2"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@mipmap/ic_launcher"
android:layout_gravity="center"
/>
<ImageView
android:id="@+id/iv_image3"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@mipmap/ic_launcher"
android:layout_gravity="right"
/>
</merge>
看一下LayoutInflate的具體處理
...
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
...
可以看到這里直接進(jìn)入rInflate,實際上就是繼續(xù)解析一下標(biāo)簽,也就是說inflate遇到merge之后就直接pass攀圈,然后把內(nèi)部的視圖添加到merge外部的視圖當(dāng)中
測量優(yōu)化
我們知道視圖在使用的時候必須要進(jìn)行測量,具體的意義就是要確定子視圖的大小球凰,那么這一塊的耗時也會影響到一幀的流暢性甩挫,也應(yīng)該是優(yōu)化考慮的一部分
比方說LinearLayout和RelativeLayout,最明顯的區(qū)別就是LinearLayout對內(nèi)部的子視圖只測量一次,而RelativeLayout要對內(nèi)部的子視圖測量兩次,所以說測量的速度上面來說LinearLayout會優(yōu)于RelativeLayout攻走,布局的話因為RelativeLayout在測量的時候就計算好了子視圖的位置,所以相對LinearLayout來說可能會快一點(diǎn)。
結(jié)論:
從使用的角度來說,如果可以單一的使用LinearLayout或者RelativeLayout的話確實是比較好的選擇,否則可以考慮通過自定義視圖的方式來實現(xiàn)測量和布局,自己實現(xiàn)可以避免了大量的無意義的計算操作拭卿,效率會更好
總結(jié)
UI這塊的優(yōu)化相對比較復(fù)雜勺鸦,具體的情況還是要做深入的分析,個人認(rèn)為原則上就是盡量降低視圖層級辫继,能自己寫一個ViewGroup直接擺好的那就最好舵变,如果時間充裕的話熙兔,也可以考慮通過Systrace來進(jìn)行對比,選擇合理方案泻拦,這一節(jié)講了視圖層級優(yōu)化闹瞧,下一節(jié)看看具體的工具
文章系列:
基本的優(yōu)化總結(jié)(一)
基本的優(yōu)化總結(jié)(二)
基本的優(yōu)化總結(jié)(三)
基本的優(yōu)化總結(jié)(四)
基本的優(yōu)化總結(jié)(五)
基本的優(yōu)化總結(jié)(六)
基本的優(yōu)化總結(jié)(七)