1. 程式人生 > >android 自定義View之View的測量(onMeasure()方法)

android 自定義View之View的測量(onMeasure()方法)

       在自定義控制元件的過程中,系統在繪製View前,必須對View進行測量,已使後面的onLayout(設定View的放置位置)能夠順利進行。而對VIew的測量的過程則是在onMeasure()中進行的。可能這時有的同學就發現問題了,說,自己以前自定義的View沒有重寫onMeasure()方法,仍然可以正常執行,這是因為什麼呢?

       讓我們先從頭說起,android系統給我們提供了一個設計短小精悍卻功能強大的類———MeasureSpec類,通過它來幫助我們測量View;

       測量規格,包含測量要求和尺寸的資訊,有三種模式

  • UNSPECIFIED
    父檢視不對子檢視有任何約束,它可以達到所期望的任意尺寸。比如 ListView、ScrollView,一般自定義 View 中用不到。

  • EXACTLY
    父檢視為子檢視指定一個確切的尺寸,而且無論子檢視期望多大,它都必須在該指定大小的邊界內,對應的屬性為 match_parent 或具體值,比如 100dp,父控制元件可以通過MeasureSpec.getSize(measureSpec)直接得到子控制元件的尺寸。

  • AT_MOST
    父檢視為子檢視指定一個最大尺寸。子檢視必須確保它自己所有子檢視可以適應在該尺寸範圍內,對應的屬性為 wrap_content,這種模式下,父控制元件無法確定子 View 的尺寸,只能由子控制元件自己根據需求去計算自己的尺寸,這種模式就是我們自定義檢視需要實現測量邏輯的情況。

相信從上面的三種模式就可以解答上面有些同學的疑問了,下面揭曉答案,View類的onMeasure()方法只支援EXACTLY模式,所以如果在自定義控制元件的時候不重寫onMeasure()方法的話,就只能使用EXACTLY模式。控制元件可以響應你指定的具體的寬高值或者是match_parent屬性,(這就像是上面的同學說的那樣,沒有重寫onMeasure()方法),而如果要讓自定義View支援wrap_content屬性,那麼就必須需要重寫onMeasure()方法來指定wrap_content時的大小。

      下面我們來看一個簡單的例項,演示如何進行View的測量,首先要重寫onMeasure()方法,該方法如下所示。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
在IDE中按住Ctrl鍵檢視super。onMeasure()方法。可以發現,系統最終呼叫setMeasuredDimension(int measuredWidth,int measuredHeight)方法將測量後的寬高值設定進去,從而完成測量工作,所以在重寫onMeasure()方法後,最終要做的工作是把測量後的寬高值作為引數設定給setMeasuredDimension()方法。

程式碼如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
}

在onMeasure()方法中,我們使用自定義的measureWidth()和measuredHeight()方法,對寬高進行重新的定義,引數分別是寬和高的MeasureSpec物件,MeasureSpec物件中包含了測量的模式和測量值的大小。

以measureWidth()為例,第一步,從MeasureSpc物件中提取出具體的測量模式和大小

int specMode=MeasureSpec.getMode(widthMeasureSpec);
int specSize=MeasureSpec.getSize(widthMeasureSpec);

接下來通過判斷測量的模式,給出不同的測量值。當specMode為EXACTLY時,直接使用指定的specSize即可;當specMode為其他兩個模式時,需要給它一個預設的大小。特別的,如果指定wrap_content屬性,即AT_MOST模式,即需要取出我們指定的大小和specSize中最小的一個作為最後的測量值,measureWidth()方法的程式碼如下所示,

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 {
        //這樣,當時用wrap_content時,View就獲得一個預設值200px,而不是填充整個父佈局。
result = 200;
        if (specMode == MeasureSpec.AT_MOST) {
            result = Math.min(result, specSize);
}
    }

    return result;
}

好了,今天的View的測量就講到這裡,後面有時間會講一下View的繪製流程。