今天分享一些layout布局書寫中的一些技巧界轩,希望看過之后你也一樣可以寫出性價(jià)比高的布局。我個(gè)人的目標(biāo)是用最少的View寫出一樣效果的布局衔瓮。因?yàn)槲蚁嘈臯iew的數(shù)量減少伴隨著的就是層級(jí)的減少浊猾。從而達(dá)到結(jié)構(gòu)清晰,渲染速度快的效果热鞍。順著這個(gè)邏輯葫慎,我將優(yōu)化分為重用、合并薇宠、按需載入偷办。
重用
< include/>
< include>標(biāo)簽可以在一個(gè)布局中引入另外一個(gè)布局,這個(gè)的好處顯而易見澄港。類似于我們經(jīng)常用到的工具類椒涯,隨用隨調(diào)。便于統(tǒng)一修改使用回梧。
舉例說明:首先寫一個(gè)公共的布局title_bar.xml废岂,app中常用的標(biāo)題欄。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:background="@color/background"
android:layout_height="48dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:src="@drawable/icon_back"/>
<TextView
tools:text="標(biāo)題"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="18sp"
android:textColor="@color/white" />
<TextView
tools:text="確定"
android:layout_width="wrap_content"
android:gravity="center"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:textSize="16sp"
android:textColor="@color/white" />
</RelativeLayout>
預(yù)覽:
下來activity_main.xml調(diào)用它:
<?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">
<include layout="@layout/title_bar"/>
</RelativeLayout>
運(yùn)行后效果和預(yù)覽一樣一樣的狱意。當(dāng)然我們也可以在< include>標(biāo)簽當(dāng)中重新設(shè)置寬高等layout屬性湖苞。
合并
減少嵌套
首先我們心中要有一個(gè)大原則:盡量保持布局層級(jí)的扁平化。在這個(gè)大原則下我們要知道:
在不影響層級(jí)深度的情況下详囤,使用LinearLayout而不是RelativeLayout财骨。因?yàn)镽elativeLayout會(huì)讓子View調(diào)用2次onMeasure,LinearLayout 在有weight時(shí)纬纪,才會(huì)讓子View調(diào)用2次onMeasure蚓再。Measure的耗時(shí)越長那么繪制效率就低滑肉。
如果非要是嵌套包各,那么盡量避免RelativeLayout嵌套R(shí)elativeLayout。這簡直就是惡性循環(huán)靶庙,喪心病狂问畅。
實(shí)現(xiàn)方法就不細(xì)說了,大家都是明白人六荒。
< merge/>
< merge/>主要用來去除不必要的FrameLayout护姆。它的使用最理想的情況就是你的根布局是FrameLayout,同時(shí)沒有使用background等屬性掏击。這時(shí)可以直接替換卵皂。因?yàn)槲覀儾季滞鈱泳褪荈rameLayout,直接“合并”砚亭。
舉例說明:比如上面用到的activity_main.xml文件灯变,我們通過View Hierarchy工具看一下殴玛,如圖:
可以看到,最外層是FrameLayout添祸,下來我們修改一下滚粟。
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/title_bar"/>
</merge>
再次查看:
很明顯少了一層RelativeLayout,當(dāng)然運(yùn)行效果是一樣的刃泌。當(dāng)然如果我們不需要title_bar.xml中的綠色背景凡壤,那么可以這樣修改。
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:src="@drawable/icon_back_1"/>
<TextView
android:layout_gravity="center_horizontal"
android:text="標(biāo)題"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_centerInParent="true"
android:textSize="18sp"
android:textColor="@color/black" />
<TextView
android:text="確定"
android:layout_gravity="right"
android:layout_width="wrap_content"
android:gravity="center"
android:layout_height="48dp"
android:layout_alignParentRight="true"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:textSize="16sp"
android:textColor="@color/black" />
</merge>
運(yùn)行效果:
運(yùn)行查看層級(jí)耙替,如下圖:
結(jié)果很明顯亚侠。
用TextView同時(shí)顯示圖片和文字
這個(gè)我就不細(xì)說了,舉一個(gè)我們項(xiàng)目中的一個(gè)例子林艘,代碼一看便知盖奈。
首先要完成的效果是如下圖:
這種效果很常見,一般實(shí)現(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:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:background="@color/white"
android:layout_width="match_parent"
android:layout_height="50dp">
<ImageView
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:src="@drawable/icon_1"
android:layout_height="match_parent" />
<TextView
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="我的卡券"
android:gravity="center_vertical"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent" />
<ImageView
android:layout_marginRight="10dp"
android:src="@drawable/icon_4"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
</LinearLayout>
</LinearLayout>
效果圖:
那么我們優(yōu)化一下:
<?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">
<TextView
android:drawableLeft="@drawable/icon_1"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="我的卡券"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
</LinearLayout>
你沒有看錯(cuò),少了兩個(gè)ImageView和去除嵌套LinearLayout啥酱。效果不用說一樣一樣的爹凹。當(dāng)然EditView等也一樣的,還有屬性drawableBottom和drawableTop供你使用镶殷。同時(shí)利用代碼setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)
可以讓我們動(dòng)態(tài)去設(shè)置圖片禾酱。
使用TextView的行間距
先上我們需要實(shí)現(xiàn)的效果圖:
效果很簡單,實(shí)現(xiàn)代碼:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="100dp"
android:background="@color/white"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:padding="25dp"
android:src="@drawable/kd_1"
android:layout_width="100dp"
android:layout_height="100dp"/>
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="100dp">
<TextView
tools:text="攬件方式:上門取件"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="25dp"/>
<TextView
tools:text="快遞公司:順豐快遞"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="25dp"/>
<TextView
tools:text="預(yù)約時(shí)間:9月6日 立即取件"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="25dp"/>
<TextView
tools:text="快遞費(fèi)用:等待稱重確定價(jià)格"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="25dp"/>
</LinearLayout>
</LinearLayout>
這里我偷懶了多嵌套了一層LinearLayout绘趋,但颤陶。。陷遮。這不重要滓走,我先直接修改。
優(yōu)化后代碼:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="100dp"
android:background="@color/white"
android:layout_width="match_parent">
<ImageView
android:padding="25dp"
android:src="@drawable/kd_1"
android:layout_width="100dp"
android:layout_height="match_parent"/>
<TextView
android:textSize="14dp"
android:lineSpacingExtra="8dp"
android:gravity="center_vertical"
android:text="攬件方式:上門取件
\n快遞公司:順豐快遞\n預(yù)約時(shí)間:9月6日 立即取件
\n快遞費(fèi)用:等待稱重確定價(jià)格"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
老規(guī)矩帽馋,效果一樣一樣的搅方。可以看到我們僅僅利用Android知識(shí)庫
android:lineSpacingExtra="8dp"
這一行代碼就省去了3個(gè)TextView绽族,如果行數(shù)更多呢姨涡?是不是方便多了。
其中:lineSpacingExtra屬性代表的是行間距吧慢,他默認(rèn)是0涛漂,是一個(gè)絕對(duì)高度值。
同時(shí)還有lineSpacingMultiplier屬性检诗,它代表行間距倍數(shù)匈仗,默認(rèn)為1.0f底哗,是一個(gè)相對(duì)高度值。我們來使用一下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="100dp"
android:background="@color/white"
android:layout_width="match_parent">
<ImageView
android:padding="25dp"
android:src="@drawable/kd_1"
android:layout_width="100dp"
android:layout_height="100dp"/>
<TextView
android:textSize="14dp"
android:lineSpacingMultiplier="1.3"
android:gravity="center_vertical"
android:text="攬件方式:上門取件
\n快遞公司:順豐快遞
\n預(yù)約時(shí)間:9月6日 立即取件
\n快遞費(fèi)用:等待稱重確定價(jià)格"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
當(dāng)然了這兩條屬性可以同時(shí)使用锚沸,查看源碼可以知道跋选,他們的高度計(jì)算規(guī)則為mTextPaint.getFontMetricsInt(null) X 行間距倍數(shù) + 行間距。
使用Spannable或Html.fromHtml
這里也是舉例說明哗蜈,比如下圖效果:
如果實(shí)現(xiàn)上圖紅框中的效果前标,笨辦法就是寫三個(gè)TextView,“¥”距潘,“價(jià)格”炼列,“門市價(jià)”分別實(shí)現(xiàn),其實(shí)用一個(gè)TextVIew就可以實(shí)現(xiàn)音比,類似如下代碼:
String text = String.format("¥%1$s 門市價(jià):¥%2$s", 18.6, 22);
int z = text.lastIndexOf("門");
SpannableStringBuilder style = new SpannableStringBuilder(text);
style.setSpan(new AbsoluteSizeSpan(DisplayUtil.dip2px(mContext,14)), 0, 1,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //字號(hào)
style.setSpan(new ForegroundColorSpan(Color.parseColor("#afafaf")), z,
text.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //顏色
style.setSpan(new AbsoluteSizeSpan(DisplayUtil.dip2px(mContext,14)), z,
text.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //字號(hào)
tv.setText(style);
同樣Html.fromHtml也可以實(shí)現(xiàn)俭尖。這樣不就減少了兩個(gè)TextView了。
按需載入
ViewStub
在開發(fā)中經(jīng)常會(huì)遇到這樣的情況洞翩,會(huì)在程序運(yùn)行時(shí)動(dòng)態(tài)根據(jù)條件來決定顯示哪個(gè)View或某個(gè)布局稽犁。那么通常做法就是把用到的View都寫在布局中,然后在代碼中動(dòng)態(tài)的更改它的可見性骚亿。但是它的這樣仍然會(huì)創(chuàng)建View已亥,會(huì)影響性能。
這時(shí)就可以用到ViewStub了来屠,ViewStub是一個(gè)輕量級(jí)的View虑椎,不占布局位置,占用資源非常小俱笛。
例子:比如我們請(qǐng)求網(wǎng)絡(luò)加載列表捆姜,如果網(wǎng)絡(luò)異常或者加載失敗我們可以顯示一個(gè)提示View迎膜,上面可以點(diǎn)擊重新加載泥技。當(dāng)然一直沒有錯(cuò)誤時(shí),我們就不顯示星虹。
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
……
<ViewStub
android:layout_gravity="center"
android:id="@+id/hint_view"
android:layout_width="match_parent"
android:inflatedId="@+id/hint_view"
android:layout_height="wrap_content"
android:layout="@layout/hint_view"/>
</merge>
hint_view.xml就是這個(gè)提示View零抬,可以根據(jù)情況自己寫镊讼。
用法:
private View hintView;
if (網(wǎng)絡(luò)異常宽涌。。蝶棋。) {
if (hintView == null) {
ViewStub viewStub = (ViewStub)this.findViewById(R.id.hint_view);
hintView = viewStub.inflate();
TextView textView = (TextView) hintView.findViewById(R.id.tv);
textView.setText("網(wǎng)絡(luò)異常卸亮!");
}
hintView.setVisibility(View.VISIBLE);
}else{
if (hintView != null) {
hintView.setVisibility(View.GONE);
}
}
用法很簡單,記得一旦ViewStub可見或是被inflate了玩裙,ViewStub就不存在了兼贸,取而代之的是被inflate的Layout段直。所以它也被稱做惰性控件。
其他小技巧
用LinearLayout自帶的分割線
還記得上文用TextView同時(shí)顯示圖片和文字中的例子嗎溶诞?我們可以看到每個(gè)條目之間都是有一根分隔線的鸯檬,那么怎么實(shí)現(xiàn)呢?別人我不知道螺垢,反正我原來是用一個(gè)View設(shè)置高度實(shí)現(xiàn)的喧务。相信一定有人和我一樣。
那么老辦法我就不演示了枉圃,直接上代碼:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:divider="@drawable/divider"
android:showDividers="middle">
<TextView
android:drawableLeft="@drawable/icon_1"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="我的卡券"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
<TextView
android:drawableLeft="@drawable/icon_2"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="地址管理"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
<TextView
android:drawableLeft="@drawable/icon_3"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="檢查更新"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
</LinearLayout>
效果圖:
實(shí)現(xiàn)的核心部分其實(shí)是LinearLayout的這兩行功茴。
android:divider="@drawable/divider"
android:showDividers="middle"
其中divider.xml是分隔線樣式。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:width="1dp" android:height="1dp"/>
<solid android:color="#e1e1e1"/>
</shape>
showDividers 是分隔線的顯示位置孽亲,beginning坎穿、middle、end分別代表顯示在開始位置返劲,中間玲昧,末尾。
還有dividerPadding屬性這里沒有用到篮绿,意思很明確給divider添加padding酌呆。感興趣可以試試。
Space控件
還是接著上面的例子搔耕,如果要給條目中間添加間距隙袁,怎么實(shí)現(xiàn)呢?當(dāng)然也很簡單弃榨,比如添加一個(gè)高10dp的View菩收,或者使用android:layout_marginTop="10dp"
等方法。但是增加View違背了我們的初衷鲸睛,并且影響性能娜饵。使用過多的margin其實(shí)會(huì)影響代碼的可讀性。
這時(shí)你就可以使用Space官辈,他是一個(gè)輕量級(jí)的箱舞。我們可以看下源碼:
/**
* Space is a lightweight View subclass that may be used to create gaps between components
* in general purpose layouts.
*/
public final class Space extends View {
/**
* {@inheritDoc}
*/
public Space(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
if (getVisibility() == VISIBLE) {
setVisibility(INVISIBLE);
}
}
/**
* {@inheritDoc}
*/
public Space(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
/**
* {@inheritDoc}
*/
public Space(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* {@inheritDoc}
*/
public Space(Context context) {
//noinspection NullableProblems
this(context, null);
}
/**
* Draw nothing.
* @param canvas an unused parameter.
*/
@Override
public void draw(Canvas canvas) { }
/**
* Compare to: {@link View#getDefaultSize(int, int)}
* If mode is AT_MOST, return the child size instead of the parent size
* (unless it is too big).
*/
private static int getDefaultSize2(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
result = Math.min(size, specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension( getDefaultSize2(getSuggestedMinimumWidth(),
widthMeasureSpec),
getDefaultSize2(getSuggestedMinimumHeight(), heightMeasureSpec));
}
}
可以看到在draw方法沒有繪制任何東西,那么性能也就幾乎沒有影響拳亿。
實(shí)現(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:layout_height="match_parent"
android:orientation="vertical"
android:divider="@drawable/divider"
android:showDividers="middle|beginning|end">
<TextView
android:drawableLeft="@drawable/icon_1"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="我的卡券"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
<TextView
android:drawableLeft="@drawable/icon_2"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="地址管理"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
<Space
android:layout_width="match_parent"
android:layout_height="15dp"/>
<TextView
android:drawableLeft="@drawable/icon_3"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="檢查更新"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
</LinearLayout>
防止過度繪制
這個(gè)完全可以看鴻洋大神這篇 《Android UI性能優(yōu)化實(shí)戰(zhàn) 識(shí)別繪制中的性能問題》:http://blog.csdn.net/lmj623565791/article/details/45556391
參考
Android布局優(yōu)化:http://www.lightskystreet.com/2015/01/19/android-layout-optimize
轉(zhuǎn)載自
唯鹿原創(chuàng)的《一些你需要知道的布局優(yōu)化技巧》