2012-09-04 58 views
263

我試圖做自定義組件。我擴展了View類,並在onDraw覆寫方法中做了一些繪製。爲什麼我需要覆蓋onMeasure?如果我沒有,一切看起來都是正確的。有人可以解釋嗎?我應該如何編寫我的onMeasure方法?我見過幾個教程,但每個教程都與其他教程略有不同。有時他們最後會撥打super.onMeasure,有時候他們會用setMeasuredDimension,但沒有撥打電話。差異在哪裏?onMeasure定製視圖說明

畢竟我想使用幾個完全相同的組件。我將這些組件添加到我的XML文件中,但我不知道它們應該有多大。我想稍後設置它的位置和大小(爲什麼我需要在onMeasure中設置大小,如果在onDraw中繪製它,工作也是如此)在自定義組件類中。我什麼時候需要這樣做?

回答

649

onMeasure()是您有機會告訴Android您希望自定義視圖的大小取決於父級提供的佈局約束;這也是您的自定義視圖的機會,可以瞭解這些佈局約束條件(如果您希望在match_parent情況下的行爲與wrap_content情況下的行爲不同)。這些約束被打包到傳入該方法的MeasureSpec值中。下面是模式值的粗略的相關性:

  • EXACTLYlayout_widthlayout_height值設定爲特定值。你可能應該讓你的觀點大小。當使用match_parent時,也可以觸發這種情況,以將尺寸精確地設置爲父視圖(這與框架中的佈局相關)。
  • AT_MOST通常表示layout_widthlayout_height值設定爲match_parentwrap_content或其中需要的最大尺寸(這是佈局依賴於框架),並且父維度的大小的值。你不應該大於這個尺寸。
  • 未知通常表示layout_widthlayout_height值設置爲wrap_content,沒有任何限制。你可以是任何你想要的大小。某些佈局還會使用此回調來確定您希望的尺寸,然後再決定在第二個度量請求中實際再次傳遞給您的參數。

onMeasure()存在的契約是:setMeasuredDimension()MUST在與大小結束時調用你想的觀點是。此方法由所有框架實現調用,包括在View中找到的默認實現,因此,如果符合您的用例,則可以安全地調用super來代替。因爲該框架確實應用了默認實現,所以您可能不需要重寫此方法,但如果視圖空間小於內容(如果不存在),並且如果視圖空間小於內容你在兩個方向上用wrap_content佈局你的自定義視圖,你的視圖可能根本不顯示,因爲框架不知道它有多大!

一般來說,如果要覆蓋View而不是另一個現有的小工具,它可能是一個好主意,提供一個實現,即使是這樣的簡單:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 

    int desiredWidth = 100; 
    int desiredHeight = 100; 

    int widthMode = MeasureSpec.getMode(widthMeasureSpec); 
    int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
    int heightMode = MeasureSpec.getMode(heightMeasureSpec); 
    int heightSize = MeasureSpec.getSize(heightMeasureSpec); 

    int width; 
    int height; 

    //Measure Width 
    if (widthMode == MeasureSpec.EXACTLY) { 
     //Must be this size 
     width = widthSize; 
    } else if (widthMode == MeasureSpec.AT_MOST) { 
     //Can't be bigger than... 
     width = Math.min(desiredWidth, widthSize); 
    } else { 
     //Be whatever you want 
     width = desiredWidth; 
    } 

    //Measure Height 
    if (heightMode == MeasureSpec.EXACTLY) { 
     //Must be this size 
     height = heightSize; 
    } else if (heightMode == MeasureSpec.AT_MOST) { 
     //Can't be bigger than... 
     height = Math.min(desiredHeight, heightSize); 
    } else { 
     //Be whatever you want 
     height = desiredHeight; 
    } 

    //MUST CALL THIS 
    setMeasuredDimension(width, height); 
} 

希望幫助。

+1

嘿@Devunwired很好的解釋迄今爲止我讀的最好的。你的解釋回答了我的許多問題,並清除了一些疑問,但仍有一個問題仍然存在:如果我的自定義視圖位於ViewGroup中,並且其他視圖(無論哪種類型)ViewGroup將其所有子項爲每個探測器的LayoutParams約束,並要求每個孩子根據自己的約束來自我測量? – pharaoh

+0

是的,這就是'ViewGroup'的'measureChildren()'方法在度量/佈局過程中所做的。 – Devunwired

+34

請注意,如果您重寫任何ViewGroup子類的onMeasure,則此代碼不會執行此操作。你的子視圖不會顯示出來,全部大小都爲0x0。如果您需要重寫自定義ViewGroup的onMeasure,請更改widthMode,widthSize,heightMode和heightSize,然後使用MeasureSpec.makeMeasureSpec將它們編譯回measureSpecs,並將生成的整數傳遞給super.onMeasure。 – Alexey

4

實際上,您的答案並不完整,因爲值也取決於包裝容器。在相對的或線性的佈局的情況下,該值這樣的表現:

  • EXACTLY match_parent正是+大小母體的
  • AT_MOST在AT_MOST MeasureSpec
  • UNSPECIFIED從未WRAP_CONTENT結果觸發

在水平滾動視圖的情況下,您的代碼將工作。

+49

如果您覺得這裏有些答案不完整,請添加它,而不是給出部分答案。 –

+0

良好的onya連接到佈局如何工作,但在我的情況下onMeasure被稱爲三次我的自定義視圖。有問題的視圖有一個wrap_content高度和一個加權寬度(width = 0,weight = 1)。第一個電話有Unknown/Without,第二個電話有AT_MOST/EXACTLY,第三個電話是完全正確的。 –