在學習onMeasure方法前,首先要掌握MeasureSpec這個類。MeasureSpec 主要是通過父容器的LayoutParams上的規則生成的,它影響到了測量的時候View的寬高。
1.閱讀MeasureSpec源碼
下面開始從源碼開始閱讀,源碼上有一些註釋,我就先自己翻譯成中文:
/**
*一個MesasureSpec代表着從父容器到子控件的佈局需求。
*每一個MesureSpec都代表着寬度和高度的需求
* 一個MeasureSpec對象都由大小(size)和模式(mode)組成,模式包含了一下幾種:
* UNSPECIFIED
* 父控件對子控件沒有加任何的限制,子控件想要多大就給子控件多大。
* EXACTLY
* 子控件的大小就是父控件指定的大小。
*
* <dt>AT_MOST</dt>
*父控件給了一個最大的大小,View的大小不能超出這個大小。
*MeasureSpec 是通過int數值來實現的,這樣避免了對象的分配。
* 這個類提供了使用int來對大小(size) ,模式(mode),tuple(數組)進行封裝和解析
*/
public static class MeasureSpec {
//代表後面30位放size
private static final int MODE_SHIFT = 30;
//0x3 也就是11 右移30位,成11000000000000000000000000000000 ,後面30位放size相關的內容
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;
/**
*
*
* mode值必須爲下面中的某一個
*
* ● android.view.View.MeasureSpec#UNSPECIFIED
* ● android.view.View.MeasureSpec#EXACTLY
* ● android.view.View.MeasureSpec#AT_MOST
*
* 記住: 在Android版本17以及以下的版本中, makeMeasureSpec這個方法的實現和參數的順序無關
* 並且還會造成值溢出影響到MeasurSpec的值,android.widget.RelativeLayout就受到了這個BUG的影響。
*在API17開始,這個BUG就被修復了,並且加之以更嚴格的規則。
*
* @param size 指定的測量的大小
* @param mode 指定的測量模式
* @return 根據大小和模式返回測量規格
*/
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
/**
* 從指定的測量規格中獲取到模式(非運算清除掉後30位的內容)
*/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
/**
*從指定的測量規格中獲取到大小(非運算清除掉前2位的內容保留後30位的內容)
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
/**根據闕值調整MeasureSpec的大小*/
static int adjust(int measureSpec, int delta) {
return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec));
}
/**
* Returns a String representation of the specified measure
* specification.
*
* @param measureSpec the measure specification to convert to a String
* @return a String with the following format: "MeasureSpec: MODE SIZE"
*/
public static String toString(int measureSpec) {
int mode = getMode(measureSpec);
int size = getSize(measureSpec);
StringBuilder sb = new StringBuilder("MeasureSpec: ");
if (mode == UNSPECIFIED)
sb.append("UNSPECIFIED ");
else if (mode == EXACTLY)
sb.append("EXACTLY ");
else if (mode == AT_MOST)
sb.append("AT_MOST ");
else
sb.append(mode).append(" ");
sb.append(size);
return sb.toString();
}
}
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;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
LinearLayout中的measure過程比較複雜,還受到了父容器傳入的MeasureSpec參數的影響,這點我們從onMeasure方法的聲明就可以看到了,我們直接拉到最後一行,看
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
private void forceUniformWidth(int count, int heightMeasureSpec) {
// Pretend that the linear layout has an exact size.
int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
MeasureSpec.EXACTLY);
for (int i = 0; i< count; ++i) {
final View child = getVirtualChildAt(i);
if (child.getVisibility() != GONE) {
LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());
if (lp.width == LayoutParams.MATCH_PARENT) {
// Temporarily force children to reuse their old measured height
// FIXME: this may not be right for something like wrapping text?
int oldHeight = lp.height;
lp.height = child.getMeasuredHeight();
// Remeasue with new dimensions
measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
lp.height = oldHeight;
}
}
}
}
這裏面調用了measureChildWithMargins 來測量子控件的大小,measureChildWithMargins是在ViewGroup方法中聲明的,我們可以看到如下:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,getChildMeasureSpec方法中生成的childWidthMeasureSpec 會傳遞給child用於測量,而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 = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}