Android學習筆記之MeasureSpec
什麼是MeasureSpec
Android系統在繪製View的時候,過程是十分複雜的,其中頻繁的使用到了MeasureSpec。那麼MeasureSpec是什麼?有什麼用?簡單點說,它是一個int值的中間變數,用來儲存View的尺寸規格。再說細點,在測量過程中,系統會將View的LayoutParams根據父容器所施加的約束規則轉換成對應的MeasureSpec。
MeasureSpec代表一個32位int值,高兩位代表SpecMode,低30位代表SpecSize,SpecMode是指測試模式,而SpecSize是指在某中測試模式下的規格大小。原始碼如下:
public static class MeasureSpec { private static final int MODE_SHIFT = 30; //高兩位11 private static final int MODE_MASK = 0x3 << MODE_SHIFT; //高兩位00 public static final int UNSPECIFIED = 0 << MODE_SHIFT; //高兩位01 public static final int EXACTLY = 1 << MODE_SHIFT; //高兩位10 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); } } public static int makeSafeMeasureSpec(int size, int mode) { if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) { return 0; } return makeMeasureSpec(size, mode); } @MeasureSpecMode public static int getMode(int measureSpec) { //取高2位得模式 return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { //取低30位得規格大小 return (measureSpec & ~MODE_MASK); } }
什麼是約束規則
上面解釋MeasureSpec時,提及到父容器的約束規則,可能會讓我們聯想到 match_parent 和 wrap_content,但這裡說的約束規則並不完全是這樣。具體來說,約束規則指的是MeasureSpec中的三種模式,即SpecMode,它是通過與 match_parent 和 wrap_content 相關的邏輯判斷最後決定下來的。每一種模式的含義如下:
- UNSPECIFIED:父容器不對View有任何限制,要多大給多大,一般用於系統內部,表示一種測試狀態。
- EXACTLY:表示View的規格為確切值,即SpecSize所指定的值。它對應於match_parent和具體的數值這兩種模式。
- AT_MOST:表示View的規格不能超過某個值,具體是什麼值要看不同View的具體實現。它對應於wrap_content。
MeasureSpec和LayoutParams的關係
上面提到, 在測量過程中,系統會將View的LayoutParams根據父容器所施加的約束規則轉換成對應的MeasureSpec。這裡需要注意的是,對於頂級View(DecorView)來說,其MeasureSpec由視窗的尺寸和其自身的LayoutParams來共同決定;對於普通View來說,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams來共同決定。
對DecorView來說,建立MeasureSpec的原始碼如下:(其中desireWindowWidth和desireWindowHeight是螢幕的尺寸)
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
再看一下getRootMeasureSpec方法
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
通過上述程式碼可以得出以下結論:
- 如果DecorView的LayoutParams中的寬/高參數為match_parent,那麼它的MeasureSpec的模式為精確模式,尺寸為視窗大小
- 如果DecorView的LayoutParams中的寬/高參數為wrap_content,那麼它的MeasureSpec的模式為最大模式,最大尺寸為視窗大小
- 如果DecorView的LayoutParams中的寬/高參數為具體值(例如100dp),那麼它的MeasureSpec的模式為精確模式,尺寸為指定的具體值(100dp)
對普通View來說,View的measure過程是由它的父容器ViewGroup呼叫的,如下:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,父容器獲取子元素的佈局引數之後,通過getChildMeasureSpec方法獲取子元素的寬高MeasureSpec,然後呼叫子元素的measure方法。接著我們來看getChildMeasureSpec方法的原始碼:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
上述程式碼不難理解,總的思路是按照父容器的SpecMode分為三個分支,然後在每個分之中結合子元素本身的LayoutParams來確定子元素的MeasureSpec。通過上述程式碼可以得出以下結論:
- 當子View採用固定寬/高時,不管父容器的MeasureSpec是什麼模式,View的MeasureSpec都是精確模式並且大小為LayoutParams中指定的具體值
- 當子View的寬/高是 match_parent 時,如果父容器的模式是精確模式,那麼子View也是精確模式並且大小是父容器的剩餘空間;如果父容器的模式是最大模式,那麼子View也是最大模式並且大小不會超過父容器的剩餘空間
- 當子View的寬/高是 wrap_content 時,不管父容器是最大模式還是精確模式,子View的模式總是最大模式並且大小不會超過父容器的剩餘空間
PS:上面的總結沒有考慮UNSPECIFIED模式,是因為該模式主要用於系統內部多次Measure的情形,通常情況下我們不需要關注它。