Android View 測(cè)量與 MeasureSpec 類

前言

前段時(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"/>

最終的效果是這樣子的。

demo 效果

可以看到第三個(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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容