這是年假最后一篇筆記了怜瞒,本篇文章的內(nèi)容主要來自《android開發(fā)藝術(shù)探索》,在文章的最后有這本書的網(wǎng)上版本舔涎。
項(xiàng)目源碼
目錄
-
MeasureSpec
- SpecMode分類
- UNSPECIFIED
- EXACTLY
- AT_MOST
- MeasureSpec和LayoutParams對(duì)應(yīng)關(guān)系
- SpecMode分類
-
measure過程
- View的measure過程
1. MeasureSpec
MeasureSpec代表的是一個(gè)32位的int類型的數(shù)值杉编,31 ~ 30為測量模式(SpecMode)蚕苇,29 ~ 0(SpecSize) 為寬高的實(shí)際大小歌溉。一個(gè)完整的MeasureSpec是由SpecMode+SpecSize組合而成娃闲,可通過makeMeasureSpec()
得到MeasureSpec忱详、通過getMode()
得到SpecMode围来、通過getSize()
得到SpecSize。
//打包生成MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//解包得到SpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//解包得到SpecSize
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
SpecMode分類
模式 | 二進(jìn)制數(shù)位 | 描述 |
---|---|---|
UNSPECIFIED | 00 | 父容器不對(duì)View有任何限制匈睁,要多大給多大,一般用于系統(tǒng)內(nèi)部表示一種測量狀態(tài) |
EXACTLY | 01 | 表示父控件已經(jīng)測量出View的大小监透。View的最終大小就是SpecSize指定的大小航唆;它對(duì)應(yīng)兩種模式第一種對(duì)應(yīng)LayoutParams的match_parent ,另一種是具體的數(shù)值
|
AT_MOST | 10 | 父容器指定一個(gè)SpecSize胀蛮,View的大小不能不能超過這個(gè)值。對(duì)應(yīng)的是LayoutParams的wrap_content
|
MeasureSpec和LayoutParams對(duì)應(yīng)關(guān)系
LayoutParams配合父容器的MeasureSpec用于約束View的大小糯钙,他們兩個(gè)共同作用下 生成最終的View的MeasureSpec粪狼,從而確定View的寬高。需要注意的是頂層View和普通View的測量有所不同任岸。DecorView的MeasureSpec是由窗口的尺寸
和自身LayoutParams共同作用生成再榄,普通View是由父容器的MeasureSpec
和自身LayoutParams共同作用生成。
頂層view的MeasureSpec生成過程:
……
//獲取頂層View的寬高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
……
//生成頂層View的MeasureSpec
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// 精度模式享潜,頂層View的大小就是窗口的大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// 最大模式困鸥,大小不確定,但頂層View的大小不能超過窗口的大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// 精度模式剑按,頂層View的大小為LayoutParams的大小
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
對(duì)于普通View的measure疾就,是由ViewGroup的measure發(fā)起的澜术。ViewGroup會(huì)調(diào)用他的measureChild()
來測量子View的寬高在該方法內(nèi)部會(huì)調(diào)用getChildMeasureSpec()
獲取View的MeasureSpec。
下面為measureChild()代碼:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//獲取子View寬度MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
//獲取子View高度MeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
下面為getChildMeasureSpec()代碼:
//子View的大小會(huì)受父容器的MeasureSpec猬腰、自身的LayoutParams鸟废、View的padding以及margin影響。
public static int getChildMeasureSpec(int spec, int padding, int childDimension){
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//子元素可用大小為父容器的尺寸減去padding
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//校驗(yàn)父容器是那種模式
switch (specMode) {
// 父容器為具體精度
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//子控件的寬或高大于0漆诽,代表其設(shè)置了具體的寬高值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子元素為精度模式侮攀,占滿父容器的剩余空間
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//當(dāng)子控件為WRAP_CONTENT的時(shí)候不管父控件是精度模式還是最大
//化模式,View的模式總是最大化,并且不會(huì)超過父容器的剩余空間
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父容器為最大化模式
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// 子控件設(shè)置了具體的值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子view為精度模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子元素為最大化模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父元素為不受限制模式
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
從上面的代碼可以知道厢拭,返回View的MeasureSpec大致可以分為一下機(jī)制情況:
- 子View為
具體的寬/高
兰英,那么View的MeasureSpec都為LayoutParams中大小。 - 子View為
match_parent
供鸠,父元素為精度模式(EXACTLY)畦贸,那么View的MeasureSpec也是精準(zhǔn)模式他的大小不會(huì)超過父容器的剩余空間。 - 子View為
wrap_content
楞捂,不管父元素是精準(zhǔn)模式還是最大化模式(AT_MOST)薄坏,View的MeasureSpec總是為最大化模式并且大小不超過父容器的剩余空間。 - 父容器為UNSPECIFIED模式主要用于系統(tǒng)多次Measure的情形寨闹,一般我們不需要關(guān)心胶坠。
2. measure過程
View的measure過程
view測量的過程是由measure()方法完成。該方法不能被重寫(是final類型方法)繁堡,在該方法內(nèi)部調(diào)用了onMeasure()方法用于測量View的大小:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//設(shè)置view的寬/高
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
從上面的代碼中我們可以知道getDefaultSize()
為獲取view測量后的大小,下面為getDefaultSize()的源碼:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
getDefaultSize()放回的大小兩種沈善,第一種為當(dāng)specMode為AT_MOST、EXACTLY情況下View的大小為specSize也就是測量后的大小椭蹄,View的最終大小是在layout階段確認(rèn)下來的闻牡,不過view的測量大小和最終大小,幾乎所有情況下都是相等的绳矩。
第二種情況為specMode為UNSPECIFIED,這種模式一般用于系統(tǒng)內(nèi)部的測量過程罩润,該模式下View的大小為傳入getDefaultSize()方法的第一個(gè)參數(shù)size,從上面的代碼可以知道,Size為getSuggestedMinimumWidth()或getSuggestedMinimumHeight()返回的大小翼馆。
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
getSuggestedMinimumWidth()和getSuggestedMinimumHeight一樣割以,我們只需要分析一個(gè)即可,下面以getSuggestedMinimumWidth()為例:
返回的大小與有沒有設(shè)置背景有關(guān)写妥,當(dāng)View沒有設(shè)置背景
拳球,返回的為mMinHeight。該值為android.minWidth
指定的值(默認(rèn)為0)珍特,如果View指定了背景
祝峻,view返回的值為max(mMinWidth, mBackground.getMinimumWidth())。
public int getMinimumWidth() {
//獲取Drawable的原始高度,如果沒有原始高度返回的為-1莱找。如:ShapeDrawable無原始高度酬姆,BitmapDrawable有原始高度。
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
總結(jié):
一般我們?cè)谧远╒iew的時(shí)候需要重寫onMeasure()方法奥溺,因?yàn)閺纳厦娴膱D表中我們可以知道辞色,當(dāng)我們指定的屬性為warp_content的時(shí)候系統(tǒng)返回的是父容器剩余空間的大小,這樣就和指定的match_parent給的大小一致了浮定。下面為解決這個(gè)問題的方式:
private int mWidth = 200;
private int mHeight = 200;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSize);
} else if (heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSize, mHeight);
}
}