
前言
- 在了解自定義
View三大流程的Measure過程前,我們需要了解一個(gè)重要基礎(chǔ):MeasureSpec - 今天,我將全面解析
MeasureSpec類的相關(guān)知識(shí),希望你們會(huì)喜歡
Carson帶你學(xué)Android自定義View文章系列:
Carson帶你學(xué)Android:自定義View基礎(chǔ)
Carson帶你學(xué)Android:一文梳理自定義View工作流程
Carson帶你學(xué)Android:自定義View繪制準(zhǔn)備-DecorView創(chuàng)建
Carson帶你學(xué)Android:自定義View Measure過程
Carson帶你學(xué)Android:自定義View Layout過程
Carson帶你學(xué)Android:自定義View Draw過程
Carson帶你學(xué)Android:手把手教你寫一個(gè)完整的自定義View
Carson帶你學(xué)Android:Canvas類全面解析
Carson帶你學(xué)Android:Path類全面解析
目錄

1. 簡(jiǎn)介

2. 組成
測(cè)量規(guī)格(MeasureSpec)是由測(cè)量模式(mode)和測(cè)量大小(size)組成,共32位(int類型),其中:
- 測(cè)量模式(mode):占測(cè)量規(guī)格(MeasureSpec)的高2位;
- 測(cè)量大小(size):占測(cè)量規(guī)格(MeasureSpec)的低30位。

其中,測(cè)量模式(Mode)的類型有三種

3. 具體使用
- 測(cè)量規(guī)格(MeasureSpec)的封裝類是:MeasureSpec類
- MeasureSpec類用一個(gè)變量封裝了測(cè)量模式(mode)和測(cè)量大小(size):通過使用二進(jìn)制,將測(cè)量模式(mode)和測(cè)量大小(size)打包成一個(gè)int值,并提供了打包和解包的方法,這樣的做法是為了減少對(duì)象內(nèi)存分配和提高存取效率。具體使用如下所示:
// 1. 獲取測(cè)量模式(Mode)
int specMode = MeasureSpec.getMode(measureSpec)
// 2. 獲取測(cè)量大小(Size)
int specSize = MeasureSpec.getSize(measureSpec)
// 3. 通過Mode 和 Size 生成新的SpecMode
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
4. 源碼分析
public class MeasureSpec {
// 進(jìn)位大小 = 2的30次方
// int的大小為32位,所以進(jìn)位30位 = 使用int的32和31位做標(biāo)志位
private static final int MODE_SHIFT = 30;
// 運(yùn)算遮罩:0x3為16進(jìn)制,10進(jìn)制為3,二進(jìn)制為11
// 3向左進(jìn)位30 = 11 00000000000(11后跟30個(gè)0)
// 作用:用1標(biāo)注需要的值,0標(biāo)注不要的值。因1與任何數(shù)做與運(yùn)算都得任何數(shù)、0與任何數(shù)做與運(yùn)算都得0
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// UNSPECIFIED的模式設(shè)置:0向左進(jìn)位30 = 00后跟30個(gè)0,即00 00000000000
// 通過高2位
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// EXACTLY的模式設(shè)置:1向左進(jìn)位30 = 01后跟30個(gè)0 ,即01 00000000000
public static final int EXACTLY = 1 << MODE_SHIFT;
// AT_MOST的模式設(shè)置:2向左進(jìn)位30 = 10后跟30個(gè)0,即10 00000000000
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* makeMeasureSpec()方法
* 作用:根據(jù)提供的size和mode得到一個(gè)詳細(xì)的測(cè)量結(jié)果嗎,即measureSpec
**/
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
// measureSpec = size + mode;此為二進(jìn)制的加法 而不是十進(jìn)制
// 設(shè)計(jì)目的:使用一個(gè)32位的二進(jìn)制數(shù),其中:32和31位代表測(cè)量模式(mode)、后30位代表測(cè)量大?。╯ize)
// 例如size=100(4),mode=AT_MOST,則measureSpec=100+10000...00=10000..00100
}
/**
* getMode()方法
* 作用:通過measureSpec獲得測(cè)量模式(mode)
**/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
// 即:測(cè)量模式(mode) = measureSpec & MODE_MASK;
// MODE_MASK = 運(yùn)算遮罩 = 11 00000000000(11后跟30個(gè)0)
//原理:保留measureSpec的高2位(即測(cè)量模式)、使用0替換后30位
// 例如10 00..00100 & 11 00..00(11后跟30個(gè)0) = 10 00..00(AT_MOST),這樣就得到了mode的值
}
/**
* getSize方法
* 作用:通過measureSpec獲得測(cè)量大小size
**/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
// size = measureSpec & ~MODE_MASK;
// 原理類似上面,即 將MODE_MASK取反,也就是變成了00 111111(00后跟30個(gè)1),將32,31替換成0也就是去掉mode,保留后30位的size
}
}
5. 計(jì)算邏輯
View的MeasureSpec值計(jì)算取決于兩個(gè)因素:
- View自身的布局參數(shù)(LayoutParams)
- 父容器的測(cè)量規(guī)格(MeasureSpec)
即View的大小是由自身布局參數(shù)(LayoutParams)和父容器的測(cè)量規(guī)格(MeasureSpec)共同決定的。
MeasureSpec值的具體計(jì)算邏輯封裝在getChildMeasureSpec()里,具體計(jì)算邏輯如下源碼所示。
/**
* 源碼分析:getChildMeasureSpec()
* 作用:根據(jù)父視圖的MeasureSpec & 布局參數(shù)LayoutParams,計(jì)算單個(gè)子View的MeasureSpec
* 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams屬性 共同決定
**/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 參數(shù)說明
// * @param spec 父view的詳細(xì)測(cè)量值(MeasureSpec)
// * @param padding view當(dāng)前尺寸的的內(nèi)邊距和外邊距(padding,margin)
// * @param childDimension 子視圖的布局參數(shù)(寬/高)
//父view的測(cè)量模式
int specMode = MeasureSpec.getMode(spec);
//父view的大小
int specSize = MeasureSpec.getSize(spec);
//通過父view計(jì)算出的子view = 父大小-邊距(父要求的大小,但子view不一定用這個(gè)值)
int size = Math.max(0, specSize - padding);
//子view想要的實(shí)際大小和模式(需要計(jì)算)
int resultSize = 0;
int resultMode = 0;
//通過父view的MeasureSpec和子view的LayoutParams確定子view的大小
// 當(dāng)父view的模式為EXACITY時(shí),父view強(qiáng)加給子view確切的值
//一般是父view設(shè)置為match_parent或者固定值的ViewGroup
switch (specMode) {
case MeasureSpec.EXACTLY:
// 當(dāng)子view的LayoutParams>0,即有確切的值
if (childDimension >= 0) {
//子view大小為子自身所賦的值,模式大小為EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
// 當(dāng)子view的LayoutParams為MATCH_PARENT時(shí)(-1)
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子view大小為父view大小,模式為EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
// 當(dāng)子view的LayoutParams為WRAP_CONTENT時(shí)(-2)
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子view決定自己的大小,但最大不能超過父view,模式為AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 當(dāng)父view的模式為AT_MOST時(shí),父view強(qiáng)加給子view一個(gè)最大的值。(一般是父view設(shè)置為wrap_content)
case MeasureSpec.AT_MOST:
// 道理同上
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 當(dāng)父view的模式為UNSPECIFIED時(shí),父容器不對(duì)view有任何限制,要多大給多大
// 多見于ListView、GridView
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// 子view大小為子自身所賦的值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 因?yàn)楦竩iew為UNSPECIFIED,所以MATCH_PARENT的話子類大小為0
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 因?yàn)楦竩iew為UNSPECIFIED,所以WRAP_CONTENT的話子類大小為0
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
總結(jié)如下:

其中的規(guī)律總結(jié):(以子View為標(biāo)準(zhǔn),橫向觀察)

由于
UNSPECIFIED模式適用于系統(tǒng)內(nèi)部多次measure情況,很少用到,故此處不討論
- 注
區(qū)別于頂級(jí)View(即DecorView)的測(cè)量規(guī)格MeasureSpec計(jì)算邏輯:取決于 自身布局參數(shù) & 窗口尺寸

6. 總結(jié)
- 本文對(duì)自定義
View繪制流程中Measure過程的基礎(chǔ)MeasureSpec類進(jìn)行了全面介紹。 - Carson帶你學(xué)Android自定義View文章系列:
Carson帶你學(xué)Android:自定義View基礎(chǔ)
Carson帶你學(xué)Android:一文梳理自定義View工作流程
Carson帶你學(xué)Android:自定義View繪制準(zhǔn)備-DecorView創(chuàng)建
Carson帶你學(xué)Android:自定義View Measure過程
Carson帶你學(xué)Android:自定義View Layout過程
Carson帶你學(xué)Android:自定義View Draw過程
Carson帶你學(xué)Android:手把手教你寫一個(gè)完整的自定義View
Carson帶你學(xué)Android:Canvas類全面解析
Carson帶你學(xué)Android:Path類全面解析
歡迎關(guān)注Carson_Ho的簡(jiǎn)書
不定期分享關(guān)于安卓開發(fā)的干貨,追求短、平、快,但卻不缺深度。
