前言
前段時(shí)間寫一個(gè)項(xiàng)目笛坦,在布局中出現(xiàn)了 ScrollView 嵌套 ListView,導(dǎo)致 ListView 只能顯示出第一個(gè) item,在網(wǎng)上查了一下,發(fā)現(xiàn)其中一種解決方案代碼量非常少甚垦,是通過自定義一個(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 的測量模式(mode)修改為 AT_MOST。
View 的測量
Android 中的顯示的按鈕闭翩、文本框等組件挣郭,都是需要系統(tǒng)繪制出來的,而在繪制之前疗韵,系統(tǒng)就需要知道每一個(gè)組件的大小兑障,寬高各是多少像素。而這些操作都是在 onMeasure() 方法中進(jìn)行的蕉汪,onMeasure() 方法可以得到 xml 布局文件中配置的寬高流译,然后可以自定義一些操作,最后調(diào)用 setMeasuredDimension() 方法者疤。
onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法有兩個(gè)參數(shù)福澡,這兩個(gè)參數(shù)都是 int 型的。雖然說是 int 型的參數(shù)驹马,但是每個(gè)參數(shù)都包含 2 個(gè)信息革砸,例如 widthMeasureSpec 這個(gè) int 值的高 2 位為測量的模式除秀,低 30 位為測量的大小,也就是 View 的寬的大小算利。之所以使用 int 類型册踩,而不是定義一個(gè)專門的類,Google 官方的解釋如下:
MeasureSpecs are implemented as ints to reduce object allocation.(減少對象分配)
測量模式有三種:
- EXACTLY 精確模式
如果我們將 View 的 layout_width 屬性或 layout_height 屬性設(shè)置為 match_parent 或者具體的值效拭,如 50 dp 等暂吉,這是,測量模式就會(huì)被指定為 EXACTLY缎患。 - AT_MOST 最大值模式
當(dāng)我們將 View 的 layout_width 屬性或 layout_height 屬性指定為 wrap_content慕的,測量模式就會(huì)被指定為 AT_MOST,這時(shí) View 大小會(huì)隨著它的子 View 大小的變化而變化较锡。 - UNSPECIFIED 不指定模式
一般系統(tǒng)不會(huì)指定該模式业稼,只有在自定義 View 時(shí)才會(huì)使用。
MeasureSpec 類
MeasureSpec 類封裝了一些對 MeasureSpecs(指 onMeasure() 方法參數(shù)蚂蕴,int 類型的那個(gè)值) 的一些操作低散,比如 getMode(int) 從 int 類型的值中取出測量模式(也就是高 2 位),getSize(int) 從 int 類型的值中取出長度值(也就是低 30 位)骡楼,makeMeasureSpec(int size, int mode) 根據(jù) size 和 mode 獲得 MeasureSpecs(int 類型的值)熔号。
demo
寫了一個(gè) demo 來理解一下 View 測量的流程。
先新建一個(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 比較特別弟头,我們設(shè)置了寬度和長度為 wrap_content,但是它的寬度與高度并沒有適應(yīng)內(nèi)容的長度涉茧。這是因?yàn)槲覀冊诟矊?onMeasure() 方法時(shí)赴恨,其中有一段是這樣的:
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
當(dāng)設(shè)置布局寬度或高度為 wrap_content 時(shí),測量模式就會(huì)是 AT_MOST伴栓,在代碼中伦连,我們將 TextView 的寬度和高度限制在了 200 以內(nèi),超過 200 仍然設(shè)置 200钳垮。