乙個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 定義乙個外部介面,將開關狀態傳出去 新增設定介面物件的方法,外部進行呼叫 ...