自定義組件之淺談onMeasure

一、默認處理

View類默認實現:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
          getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

默認實現中,調用了setMeasuredDimension( )設置測量后的寬高,這個方法需要兩個參數,也就是我們測量后的寬、高信息.

二、widthMeasureSpec、heightMeasureSpec

1、寬、高信息

不管是重寫onMeasure還是默認的實現中,我們都可以看到兩個int類型值:widthMeasureSpec、heightMeasureSpec.

這兩個int類型值就是寬和高信息,注意:無論寬度信息(widthMeasureSpec)還是高度信息(widthMeasureSpec),都包含兩種數據------模式、大小,模式是根據wrap_content、match_parent、具體值來確定的,大小則是根據不同模式進行計算.

2、模式

我們在xml中定義layout_width、layout_height屬性時有3種情況:

wrap_content
match_parent
具體值

寬、高信息中的模式也有三個不同的常量值:

MeasureSpec.EXACTLY
當組件的尺寸為match_parent或具體值時用該常量值表示此時的模式

MeasureSpec.AT_MOST
當組件的尺寸為wrap_content時用該常量值表示此時的模式

MeasureSpec.UNSPECIFIED
未指定尺寸,一般用不到

很好理解,EXACTLY譯為確定的,使用match_parent表示組件大小匹配父容器,而父容器本身也是一個組件,它會計算出自己的大小,我們不需要重復去計算,所以無論是match_parent還是具體值,組件大小都是確定的,所以這兩種情況都用MeasureSpec.EXACTLY來表示.

使用wrap_content(包裹內容)時,此時組件的大小需要根據內容確定,只要在父容器指定的最大尺寸之內就行了,用MeasureSpec.AT_MOST來表示.

MeasureSpec.UNSPECIFIED表示父容器對子視圖不進行任何約束,子視圖可以是它想要的任何大小,一般用不到.

3、獲取模式、大小

widthMeasureSpec、heightMeasureSpec都是一個int類型值,那么一個int
怎么保存模式、大小這兩種數據呢?

我們都知道:int類型占用4個字節,一共32位. 而widthMeasureSpec和heightMeasureSpec的前2位代表模式,后30位表示大小.

ok,既然知道怎么表示的,那么我們可以通過位運算來獲取模式和大?。?/p>

模式  int mode = widthMeasureSpec & 0x3 << 30;
大小  int size = widthMeasureSpec & ~0x3 << 30;

每次都要進行位運算很麻煩,所以系統提供了MeasureSpec類,這個類有3個常量(就是上面的三種模式)以及一些靜態方法:

三種模式:
          MeasureSpec.EXACTLY
          MeasureSpec.AT_MOST
          MeasureSpec.UNSPECIFIED

方法:
        public static int makeMeasureSpec(int size, int mode) 
        傳入尺寸大小和模式生成一個包含這兩個信息的int類型值

        從一個包含模式、大小的int類型值中獲取模式、大小
        public static int getMode(int measureSpec)
        public static int getSize(int measureSpec)

三、正方形View

public class XView extends View {

    public XView(Context context) {
        this(context, null);
    }

    public XView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public XView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = myMeasure(0, widthMeasureSpec);
        int height = myMeasure(0, heightMeasureSpec);

        // 和系統onMeasure() 方法不同的是:我添加了下面的if-else語句
        if (width > height) {
            width = height;
        } else {
            height = width;
        }

        setMeasuredDimension(width, height);
    }

    /**
     *雖然寫著方法名是myMeasure,但是其實這就是View類getDefaultSize()方法的源碼
     *注意看文章開頭,setMeasuredDimension()需要的兩個參數:寬、高信息都是通過這個方法生成的
     */
    private int myMeasure(int defaultValue, int measureSpec) {
        int result = defaultValue;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = defaultValue;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }

}

上面定義了一個正方形的XView,不論XView在布局文件中的寬、高如何定義,顯示在界面上始終是一個正方形的View(你需要設置一個背景顏色,不然看不出效果),感興趣的可以運行查看效果.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。