簡要
今天來聊聊MeasureSpec,記得剛接觸的也感覺很難理解,知其然不知其所以然。MeasureSpec其實在面試中還經常會被問到,如果沒有真正去理解它,不論是後續的開發或者面試中,難免會成爲絆腳石。遂今天在此聊聊這個,可能以下文中理解有所偏頗。如果有什麼不到之處或者看完還有不太理解的地方,可以在文章後面寫下你的評論一起去探討。
一說到MeasureSpec,大家本能反應是其是由高兩位的mode和低30位的size組成的32位的一個int值,但要是再深入問一些可能回答不出來,例如:MeasureSpec爲什麼由mode+size的方式組成?MeasureSpec的值跟什麼有關?或者父View與子View的MeasureSpec值的關係?自定義Viewd的onMeasure()方法要不要重寫?你還在爲這些面試中經常問的問題爲難到嗎?相信看完下文,心裏多多少少都會有個答案。
組成
其實MeasureSpec是View的一個內部類,真正的身份就是幫助View完成測量功能。MeasureSpec類中最主要的部分由5個變量和3個方法組成,接下來根據原碼中的代碼慢慢一層層剖析。
變量
五個成員變量:
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
方法
三個方法
public static int makeMeasureSpec( int size,int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
舉例:
位運算符號:
<< :左移位運算 符號左邊是值,符號右邊是左移的位數
& :與運算 相同位的值同爲1則爲,否爲0
~ :優先級比算數運算符、關係運算符、邏輯運算符和其他運算符都高
解釋
- MeasureSpec由mode+size的形式組合而成32位int值的好處是減少變量的創建
- MODE_MASK、UNSPECIFIED、EXACTLY 、AT_MOST 四個變量均左移30位,剛好高兩位組成2*2排列組合,由位運算特性mode=MODE_MASK&mode
- makeMeasureSpec()方法目的就是通過mode和size組成MesureSpec的值,if條件判斷SDK版本是否低於17,但是結果是相同的,只是高版本採用位運算的方式獲取。
- getMode()方法是獲取measureSpec的mode值,由於MODE_MASK低30位全是0,因此MODE_MASK&measureSpec計算的結果低30位爲0,又因爲measureSpec的高兩位爲mode值,又因爲MODE_MASK的高兩位全爲1,因此MODE_MASK&measureSpec高兩位的值爲mode高兩位的值,再加上低30位的0,因此MODE_MASK&measureSpec的結果就是mode值。
- getSize()方法是獲取measureSpec的size值,由於~ 位運算優先級高於&運算,因此先~運算再&運算。~MODE_MASK運算之後高兩位爲0,低30位爲1。~MODE_MASK&measureSpec的高兩位只能爲0,又因爲measureSpec的低30位size值,因此~MODE_MASK&measureSpec的低30位爲size值。
來源和決定因素
上面的組成部分講解了MeasureSpec內部結構,其中mode與size參數的意義和如何獲取。接下來說一說其如何幫助測量View的。我們都知道整個View體系是樹狀結構的,測量是由上至下的過程,View的測量功能由onMeasure()和measure()方法完成,其中onMeasure()屬於回調方法,其結果再一層層回溯給頂層View。其中開發中我們只需要關注onMeasure()方法。onMeasure()方法中最重要的參數就是MeasureSpec值,接下來看一下自定義View的onMeasure()方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int specMode = MeasureSpec.getMode(widthMeasureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY:
Log.e("TAG", "ChildView:MeasureSpec.EXACTLY");
break;
case MeasureSpec.AT_MOST:
Log.e("TAG", "ChildView:MeasureSpec.AT_MOST");
break;
case MeasureSpec.UNSPECIFIED:
Log.e("TAG", "ChildView:MeasureSpec.UNSPECIFIED");
break;
}
}
說明:
本文以下內容均只針對寬方向上來說
- 源頭:widthMeasureSpec值是由其父類傳遞過來的
- 值 :widthMeasureSpec(自身)=super.widthMeasureSpec(父)+this.LayoutParams(自身)結果得來的
- LayoutParams是ViewGroup的靜態內部類,由widthparam+widthparam組成,LayoutParams的類型是其父類的類型,值是自身的,常用的ViewGroup都重寫,新增Gravity方位值。其值分爲三類,MATCH_PARENT(-1)、WRAP_CONTENT(-2)、具體的dp值,後面具體的dp值用dp代替
上圖說明:
1、上圖是自定義View沒重寫onMeasure()的情況,謹記!!!
2、childView括號中的三個值從左到右爲:LayoutParams的值、MeasureSpec的mode值(已經確定的)、測量過後的自身size值(此時稱爲measuredWidth)
3、正常情況下UNSPECIFIED模式用不到,所以自定義View的時候可以不做處理
4、getMeasuredWidth()≠getWidth(),getMeasuredWidth()在測量之後就有值了,但是getWidth()是沒有值的
5、只有自身mode爲EXACTLY時,測量的size值爲自己設置的,mode爲AT_MOST的時候size全是父類的值
總結
這裏做一下總結,其中有兩部分組成,第一部分是內部的組成關係,組成關係中最主要的是幾個位運算和幾個參數的意義。第二部分是值的來源和決定因素,onMeasure()方法中值由父類傳遞過來,其值是根據父類的mode和自身的LayoutParams決定。如果還感覺很模糊,不妨自己去寫個例子去驗證一下,這樣可以加深印象,最重要的還是要理解。