前言
- 在了解自定義
View
三大流程的Measure
過程前,我們需要了解一個重要基礎(chǔ):MeasureSpec
- 今天茁肠,我將全面解析
MeasureSpec
類的相關(guān)知識患民,希望你們會喜歡
Carson帶你學(xué)Android自定義View文章系列:
Carson帶你學(xué)Android:自定義View基礎(chǔ)
Carson帶你學(xué)Android:一文梳理自定義View工作流程
Carson帶你學(xué)Android:自定義View繪制準備-DecorView創(chuàng)建
Carson帶你學(xué)Android:自定義View Measure過程
Carson帶你學(xué)Android:自定義View Layout過程
Carson帶你學(xué)Android:自定義View Draw過程
Carson帶你學(xué)Android:手把手教你寫一個完整的自定義View
Carson帶你學(xué)Android:Canvas類全面解析
Carson帶你學(xué)Android:Path類全面解析
目錄
1. 簡介
2. 組成
測量規(guī)格(MeasureSpec)是由測量模式(mode)和測量大小(size)組成,共32位(int類型)垦梆,其中:
- 測量模式(mode):占測量規(guī)格(MeasureSpec)的高2位匹颤;
- 測量大小(size):占測量規(guī)格(MeasureSpec)的低30位。
其中托猩,測量模式(Mode)的類型有三種
3. 具體使用
- 測量規(guī)格(MeasureSpec)的封裝類是:MeasureSpec類
- MeasureSpec類用一個變量封裝了測量模式(mode)和測量大小(size):通過使用二進制印蓖,將測量模式(mode)和測量大小(size)打包成一個int值,并提供了打包和解包的方法京腥,這樣的做法是為了減少對象內(nèi)存分配和提高存取效率赦肃。具體使用如下所示:
// 1. 獲取測量模式(Mode)
int specMode = MeasureSpec.getMode(measureSpec)
// 2. 獲取測量大小(Size)
int specSize = MeasureSpec.getSize(measureSpec)
// 3. 通過Mode 和 Size 生成新的SpecMode
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
4. 源碼分析
public class MeasureSpec {
// 進位大小 = 2的30次方
// int的大小為32位公浪,所以進位30位 = 使用int的32和31位做標志位
private static final int MODE_SHIFT = 30;
// 運算遮罩:0x3為16進制他宛,10進制為3,二進制為11
// 3向左進位30 = 11 00000000000(11后跟30個0)
// 作用:用1標注需要的值欠气,0標注不要的值厅各。因1與任何數(shù)做與運算都得任何數(shù)、0與任何數(shù)做與運算都得0
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// UNSPECIFIED的模式設(shè)置:0向左進位30 = 00后跟30個0预柒,即00 00000000000
// 通過高2位
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// EXACTLY的模式設(shè)置:1向左進位30 = 01后跟30個0 队塘,即01 00000000000
public static final int EXACTLY = 1 << MODE_SHIFT;
// AT_MOST的模式設(shè)置:2向左進位30 = 10后跟30個0,即10 00000000000
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* makeMeasureSpec()方法
* 作用:根據(jù)提供的size和mode得到一個詳細的測量結(jié)果嗎宜鸯,即measureSpec
**/
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
// measureSpec = size + mode人灼;此為二進制的加法 而不是十進制
// 設(shè)計目的:使用一個32位的二進制數(shù),其中:32和31位代表測量模式(mode)顾翼、后30位代表測量大型斗拧(size)
// 例如size=100(4),mode=AT_MOST适贸,則measureSpec=100+10000...00=10000..00100
}
/**
* getMode()方法
* 作用:通過measureSpec獲得測量模式(mode)
**/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
// 即:測量模式(mode) = measureSpec & MODE_MASK;
// MODE_MASK = 運算遮罩 = 11 00000000000(11后跟30個0)
//原理:保留measureSpec的高2位(即測量模式)灸芳、使用0替換后30位
// 例如10 00..00100 & 11 00..00(11后跟30個0) = 10 00..00(AT_MOST),這樣就得到了mode的值
}
/**
* getSize方法
* 作用:通過measureSpec獲得測量大小size
**/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
// size = measureSpec & ~MODE_MASK;
// 原理類似上面拜姿,即 將MODE_MASK取反烙样,也就是變成了00 111111(00后跟30個1),將32,31替換成0也就是去掉mode蕊肥,保留后30位的size
}
}
5. 計算邏輯
View的MeasureSpec值計算取決于兩個因素:
- View自身的布局參數(shù)(LayoutParams)
- 父容器的測量規(guī)格(MeasureSpec)
即View的大小是由自身布局參數(shù)(LayoutParams)和父容器的測量規(guī)格(MeasureSpec)共同決定的谒获。
MeasureSpec值的具體計算邏輯封裝在getChildMeasureSpec()里蛤肌,具體計算邏輯如下源碼所示。
/**
* 源碼分析:getChildMeasureSpec()
* 作用:根據(jù)父視圖的MeasureSpec & 布局參數(shù)LayoutParams批狱,計算單個子View的MeasureSpec
* 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams屬性 共同決定
**/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 參數(shù)說明
// * @param spec 父view的詳細測量值(MeasureSpec)
// * @param padding view當前尺寸的的內(nèi)邊距和外邊距(padding,margin)
// * @param childDimension 子視圖的布局參數(shù)(寬/高)
//父view的測量模式
int specMode = MeasureSpec.getMode(spec);
//父view的大小
int specSize = MeasureSpec.getSize(spec);
//通過父view計算出的子view = 父大小-邊距(父要求的大小裸准,但子view不一定用這個值)
int size = Math.max(0, specSize - padding);
//子view想要的實際大小和模式(需要計算)
int resultSize = 0;
int resultMode = 0;
//通過父view的MeasureSpec和子view的LayoutParams確定子view的大小
// 當父view的模式為EXACITY時,父view強加給子view確切的值
//一般是父view設(shè)置為match_parent或者固定值的ViewGroup
switch (specMode) {
case MeasureSpec.EXACTLY:
// 當子view的LayoutParams>0赔硫,即有確切的值
if (childDimension >= 0) {
//子view大小為子自身所賦的值炒俱,模式大小為EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
// 當子view的LayoutParams為MATCH_PARENT時(-1)
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子view大小為父view大小,模式為EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
// 當子view的LayoutParams為WRAP_CONTENT時(-2)
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子view決定自己的大小爪膊,但最大不能超過父view权悟,模式為AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 當父view的模式為AT_MOST時,父view強加給子view一個最大的值推盛。(一般是父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;
// 當父view的模式為UNSPECIFIED時峦阁,父容器不對view有任何限制,要多大給多大
// 多見于ListView耘成、GridView
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// 子view大小為子自身所賦的值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 因為父view為UNSPECIFIED拇派,所以MATCH_PARENT的話子類大小為0
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 因為父view為UNSPECIFIED,所以WRAP_CONTENT的話子類大小為0
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
總結(jié)如下:
其中的規(guī)律總結(jié):(以子View
為標準凿跳,橫向觀察)
由于
UNSPECIFIED
模式適用于系統(tǒng)內(nèi)部多次measure
情況件豌,很少用到,故此處不討論
- 注
區(qū)別于頂級View
(即DecorView
)的測量規(guī)格MeasureSpec
計算邏輯:取決于 自身布局參數(shù) & 窗口尺寸
6. 總結(jié)
- 本文對自定義
View
繪制流程中Measure
過程的基礎(chǔ)MeasureSpec
類進行了全面介紹控嗜。 - Carson帶你學(xué)Android自定義View文章系列:
Carson帶你學(xué)Android:自定義View基礎(chǔ)
Carson帶你學(xué)Android:一文梳理自定義View工作流程
Carson帶你學(xué)Android:自定義View繪制準備-DecorView創(chuàng)建
Carson帶你學(xué)Android:自定義View Measure過程
Carson帶你學(xué)Android:自定義View Layout過程
Carson帶你學(xué)Android:自定義View Draw過程
Carson帶你學(xué)Android:手把手教你寫一個完整的自定義View
Carson帶你學(xué)Android:Canvas類全面解析
Carson帶你學(xué)Android:Path類全面解析
歡迎關(guān)注Carson_Ho的簡書
不定期分享關(guān)于安卓開發(fā)的干貨茧彤,追求短、平疆栏、快曾掂,但卻不缺深度。