前言
前段時(shí)間寫一個(gè)項(xiàng)目,在布局中出現(xiàn)了 ScrollView 嵌套 ListView,導(dǎo)致 ListView 只能顯示出第一個(gè) item,在網(wǎng)上查了一下,發(fā)現(xiàn)其中一種解決方案代碼量非常少,是通過(guò)自定義一個(gè) ListView,覆寫其中的 onMeasure() 方法。代碼如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
代碼中最重要的地方就是將 View 的測(cè)量模式(mode)修改為 AT_MOST。
View 的測(cè)量
Android 中的顯示的按鈕、文本框等組件,都是需要系統(tǒng)繪制出來(lái)的,而在繪制之前,系統(tǒng)就需要知道每一個(gè)組件的大小,寬高各是多少像素。而這些操作都是在 onMeasure() 方法中進(jìn)行的,onMeasure() 方法可以得到 xml 布局文件中配置的寬高,然后可以自定義一些操作,最后調(diào)用 setMeasuredDimension() 方法。
onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法有兩個(gè)參數(shù),這兩個(gè)參數(shù)都是 int 型的。雖然說(shuō)是 int 型的參數(shù),但是每個(gè)參數(shù)都包含 2 個(gè)信息,例如 widthMeasureSpec 這個(gè) int 值的高 2 位為測(cè)量的模式,低 30 位為測(cè)量的大小,也就是 View 的寬的大小。之所以使用 int 類型,而不是定義一個(gè)專門的類,Google 官方的解釋如下:
MeasureSpecs are implemented as ints to reduce object allocation.(減少對(duì)象分配)
測(cè)量模式有三種:
- EXACTLY 精確模式
如果我們將 View 的 layout_width 屬性或 layout_height 屬性設(shè)置為 match_parent 或者具體的值,如 50 dp 等,這是,測(cè)量模式就會(huì)被指定為 EXACTLY。 - AT_MOST 最大值模式
當(dāng)我們將 View 的 layout_width 屬性或 layout_height 屬性指定為 wrap_content,測(cè)量模式就會(huì)被指定為 AT_MOST,這時(shí) View 大小會(huì)隨著它的子 View 大小的變化而變化。 - UNSPECIFIED 不指定模式
一般系統(tǒng)不會(huì)指定該模式,只有在自定義 View 時(shí)才會(huì)使用。
MeasureSpec 類
MeasureSpec 類封裝了一些對(duì) MeasureSpecs(指 onMeasure() 方法參數(shù),int 類型的那個(gè)值) 的一些操作,比如 getMode(int) 從 int 類型的值中取出測(cè)量模式(也就是高 2 位),getSize(int) 從 int 類型的值中取出長(zhǎng)度值(也就是低 30 位),makeMeasureSpec(int size, int mode) 根據(jù) size 和 mode 獲得 MeasureSpecs(int 類型的值)。
demo
寫了一個(gè) demo 來(lái)理解一下 View 測(cè)量的流程。
先新建一個(gè) MyTextView 繼承自 TextView,覆寫其中的 onMeasure() 方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec)
);
}
private int measureHeight(int heightMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
private int measureWidth(int widthMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
然后在布局中添加 3 個(gè) MyTextView。
<cn.zheteng123.onmeasuretest.MyTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF0000"/>
<cn.zheteng123.onmeasuretest.MyTextView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#00FF00"/>
<cn.zheteng123.onmeasuretest.MyTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0000FF"
android:text="12345678998765432112345699999999999999999999"/>
最終的效果是這樣子的。

可以看到第三個(gè) TextView 比較特別,我們?cè)O(shè)置了寬度和長(zhǎng)度為 wrap_content,但是它的寬度與高度并沒(méi)有適應(yīng)內(nèi)容的長(zhǎng)度。這是因?yàn)槲覀冊(cè)诟矊?onMeasure() 方法時(shí),其中有一段是這樣的:
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
當(dāng)設(shè)置布局寬度或高度為 wrap_content 時(shí),測(cè)量模式就會(huì)是 AT_MOST,在代碼中,我們將 TextView 的寬度和高度限制在了 200 以內(nèi),超過(guò) 200 仍然設(shè)置 200。