自定義View之onMeasure 方法

2021-08-28 04:55:32 字數 3672 閱讀 2839

乙個view從建立到被繪製到螢幕上,需要完成measure(測量)、layout(布置)、draw(繪製)三個步驟,分別對應view中的measure()、layout()、draw()三個方法。網上關於這三個方法的原始碼解析文章有很多,而且一般情況下也不會去重寫它們(measure()方法還無法覆蓋),因此本文不打算將其作為重點。本文以及接下來的幾篇文章會詳細介紹和程式設計人員關係更大的onmeasure()、onlayout()與ondraw()的具體實現方法,以及過程中會涉及到的一些知識。

view中的onmeasure()方法是這樣的:

protected void onmeasure(int widthmeasurespec, int heightmeasurespec)
可以看到,它的引數是widthmeasurespec與heightmeasurespec兩個int值。這兩個引數實質上是由view的靜態內部類measurespec管理的兩個特殊的「物件」(並非真正的物件),包含了父view關於子view應當如何測量自身給出的「指示」。為了提公升效率,android系統採用位運算的方式,將模式specmode(2位)與尺寸specsize(30位)拼接成了乙個int值,並傳遞這個int值作為測量時使用的引數。 

measurespec類的實現基本都是依靠位運算,沒什麼實質性內容。直接上一些結論: 

(1)specmode分三種:unspecified、exactly、at_most。unspecified表示不指定具體測量模式,exactly表示父view希望子view的尺寸取精確值(即等於specsize),at_most表示父view希望子view的尺寸不超過specsize。 

(2)使用measurespec.getmode(int measurespec)與measurespec.getsize(int measurespec)獲取specmode與specsize。 

(3)使用measurespec.makemeasurespec(int size,int mode)生成乙個measurespec值。

下面回到onmeasure()方法。顧名思義,這個方法是在該view需要測量自身時呼叫的。具體來說,當這個view的父view對其呼叫measure()方法時,onmeasure()方法會在過程中被呼叫。下面分別看看view與viewgroup分別應當怎麼實現這個方法。

view只需要根據自身情況,計算出自己的尺寸就可以了。步驟如下: 

(1)使用measurespec.getmode()與measurespec.getsize()獲取父view要求的specmode與specsize。 

(2)根據上面的引數確定自己的實際尺寸(width與height)。一般來說,如果specmode是exactly,那麼直接取尺寸值=specsize即可。如果specmode是at_most,那麼就需要根據自身特點計算出乙個尺寸值,並保證最終尺寸值不超過specsize。當然了,你也可以完全無視父view的要求,自顧自地進行測量,不過這種方式顯然是不推薦的。 

(3)使用setmeasureddimension(int measuredwidth, int measuredheight)設定最終測量尺寸。這個方法被呼叫之後,view的getmeasuredwidth()方法與getmeasuredheight()方法才能生效(在之前呼叫會返回0)。 

實際上,對於不那麼複雜的自定義view,view類提供的預設實現已經可以滿足大部分需求了。下面看看它是怎麼做的:

protected void onmeasure(int widthmeasurespec, int heightmeasurespec)
getsuggestedminimumwidth()與getsuggestedminimumheight()是根據view是否設定了backgrounddrawable確定乙個最小尺寸值。重點看一下getdefaultsize()方法:

public static int getdefaultsize(int size, int measurespec) 

return result;

}

解釋見**注釋。這裡需要記住的是,如果使用這個方法計算尺寸值的話,at_most模式不會生效。at_most一般是在view的layout_width與layout_height為wrap_content時使用的。因此,如果想要自定義view支援wrap_content屬性,就必須自己對at_most的情況作出處理。

不同於view,viewgroup需要負責子view的測量。具體來講,就是為子view提供合適的measurespec,並呼叫子view的measure()(注意,不是onmeasure())方法。至於自身的尺寸,則需要結合更高一層的父view的指示以及子view的情況來確定。 

為了給子view提供合適的measurespec,viewgroup中提供了乙個getchildmeasurespec()方法,下面看看它的實現:

public static int getchildmeasurespec(int spec, int padding, int childdimension)  else if (childdimension == layoutparams.match_parent)  else if (childdimension == layoutparams.wrap_content) 

break;

// parent has imposed a maximum size on us

case measurespec.at_most:

if (childdimension >= 0) else if (childdimension == layoutparams.match_parent) else if (childdimension == layoutparams.wrap_content)

break;

// parent asked to see how big we want to be

case measurespec.unspecified:

if (childdimension >= 0) else if (childdimension == layoutparams.match_parent) else if (childdimension == layoutparams.wrap_content)

break;

}//noinspection resourcetype

return measurespec.makemeasurespec(resultsize, resultmode);

}

英文注釋是自帶的,已經很詳細了。引數spec是高層view提供的measurespec,padding為需要扣除的padding部分尺寸,childdimension為子view需求的尺寸(一般直接傳marginlayoutparams.width)。實質上,這個方法就是綜合考慮了高層view的指示以及低層view的需求,分9種情況構建了乙個合適的measurespec。下面的圖來自android view系統解析(下) ,任玉剛:

關於onmeasure()方法的實現差不多就這些內容了。可以看出,measurespec是父view與子view溝通的橋梁。實現onmeasure()方法的關鍵點就在於如何響應父view的measurespec,以及如何為子view構建合適的measurespec。

自定義view之自定義屬性

1.首先在res的values檔案下新建乙個名為attrs.xml檔案 在該xml檔案中編寫我們需要的屬性 declare styleable後面的name必須要與接下來要自定義的view名一致。attr 後面的name表示需要自定義的屬性,format表示這些屬性的型別 2.新建乙個類繼承text...

Android自定義控制項之自定義View 二

效果如下圖 1 自定義ringview繼承view新增其構造方法並建立畫筆 public class ringview extends view protected boolean isrunning false public ringview context context public ring...

自定義View之Switch

思路 定義類繼承view,重寫幾個用到的方法 1.三個構造方法 2.onmeasure測量 onlayout布局 ondrow繪圖 3.ontouchevent觸控事件方法 invalidate 可以高頻度的呼叫ondraw 定義乙個外部介面,將開關狀態傳出去 新增設定介面物件的方法,外部進行呼叫 ...