概述
measure过程要分为两种情况:如果是原始的View,需要通过measure 方法就完成其测量过程;如果是ViewGroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程。
ViewRootImpl
View 的整个绘制流程的启动入口可以从 ViewRootImpl 的 performTraversals 方法开始看。performTraversals 方法调用了performMeasure()方法。
// ViewRootImpl 的源码
private void performTraversals() {
....
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || dispatchApplyInsets ||
updatedConfiguration) {
// mWidth/mHeight:屏幕的宽/高 lp.width/lp.height:DecorView 的宽高
// 得到DecorView 的MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 测量过程开始
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
.....
}
}
....
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// mView(DecorView) 调用View#measure方法 进行测量宽/高
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
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;
}
View measure过程
View 的measure 过程由其measure方法来完成,measure方法是一个final类型的方法,这意味着子类不能重写此方法,在View 的measure 方法中会去调用View的onMeasure方法。
// View 的源码
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
....
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
//如果上一次的测量规格和这次不一样,则条件满足,重新测量视图View的大小
if (forceLayout || needsLayout) {
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
}
....
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
从上面的代码可以看出,首先判断和上次测量的MeasureSpec是否相等,如果不相等就重新测量。measure() 方法调用了onMeasure()方法,并且把相应的宽/高测量规格传进去。说明测量的主要的逻辑在View#onMeasure()方法中。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// setMeasuredDimension方法 设置View宽/高的测量值
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// 根据View默认大小的宽高和父View传递的测量规格重新计算View的测量宽高
public static int getDefaultSize(int size, int measureSpec) {
// size代表view的默认尺寸大小
int result = size;
// measureSpec代表父容器提供的子View的MeasureSpec
int specMode = MeasureSpec.getMode(measureSpec);
// specSize 是View 测量后的大小
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
// result代表返回的view的尺寸大小
return result;
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight :
max(mMinHeight, mBackground.getMinimumHeight());
}
从以上源码来看,如果View没有设置背景,那么View的宽度为mMinWidth,而mMinWidth对应于android:minWidth 这个属性所指定的值,因此View的宽度为android:minWidth属性所指定的值。android:minWidth属性如果不指定,那么mMinWidth则默认为0;如果View指定了背景,则返回android:minWidth和背景的最小宽度这两者中的最大值,getSuggestedMinimumWidth 和 getSuggestedMinimumHeight的返回值就是View在UNSPECIFIED情况下的测量宽/高。
// View 源码
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
public int getMinimumHeight() {
final int intrinsicHeight = getIntrinsicHeight();
return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
从以上的代码来看,返回的是Drawable的原始宽度/高度,前提是这个Drawable 有原始宽/高度,否则就返回0;举个例子,ShapeDrawable 无原始宽/高;而BitmapDrawable有原始宽/高(图片的尺寸)。
Question: 直接继承View 的自定义控件需要重写onMeasure 方法并设置wrap_content 时自身大小,否则在布局中使用 wrap_content就相当于使用 match_parent?
Why: 如果View在布局中使用wrap_content,那么它的specMode是AT_MOST模式,在这种模式下,它的宽/高等于specSize;这种情况下View的specSize是parentSize,而parentSize是父容器中目前可以使用大小,也就是父容器当前剩余的空间大小。View的宽/高就等于父容器当前剩余的空间大小。
Answer :
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode==MeasureSpec.AT_MOST &&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,mHeight);
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,heightSpecSize);
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,mHeight);
}
}
在上面的代码中,只需要给View指定一个默认的内部宽/高(mWidth 和mHeight),并在wrap_content时设置此宽/高即可。对于非wrap_content时,用系统的测量值即可。至于这个默认的内部宽/高的大小如何指定,这个没有固定的依据,根据需要灵活指定即可
ViewGroup 的measure 过程
对于ViewGroup来说,除了完成自己的measure 过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。ViewGroup是一个抽象类,因此它没有重写View 的onMeasure方法,但提供了measureChildren 的方法。
// ViewGroup 源码
// 遍历子View & 调用measureChild()进行下一步测量
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
// 调用measureChild()进行下一步的测量
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 获取子视图的布局参数
final LayoutParams lp = child.getLayoutParams();
// 根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 将计算好的子View的MeasureSpec值传入measure(),进行最后的测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
// specSize 父容器的大小
int specSize = MeasureSpec.getSize(spec);
// padding 父容器中已占用的空间大小
// size 子元素可用空间大小
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);
}
从以上的代码来看,ViewGroup在measure时,会对每一个子元素会调用measureChild方法进行测量。measureChild方法就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure方法进行测量。
下面具体分析实例:LinearLayout 源码measureHorizontal
// LinearLayout 源码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
....
// 获取垂直方向上的子View个数
final int count = getVirtualChildCount();
....
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
// 子View不可见,直接跳过该View的measure过程,
// getChildrenSkipCount()返回值恒为0
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 记录子View是否有weight属性设置,用于后面判断是否需要二次measure
totalWeight += lp.weight;
final boolean useExcessSpace = lp.width == 0 && lp.weight > 0;
if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
if (isExactly) {
mTotalLength += lp.leftMargin + lp.rightMargin;
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength +
lp.leftMargin + lp.rightMargin);
}
...
}else{
...
final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
// 该方法内部最终会调用measureChildren(),从而遍历所有子View & 测量
measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
heightMeasureSpec, 0);
if (isExactly) {
mTotalLength += childWidth + lp.leftMargin + lp.rightMargin
+ getNextLocationOffset(child);
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin
+ lp.rightMargin + getNextLocationOffset(child));
}
}
}
if (useLargestChild &&
(widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
...
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
if (isExactly) {
mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
getNextLocationOffset(child);
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +
lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
}
}
}
// mTotalLength用于存储LinearLayout在水平方向的长度
mTotalLength += mPaddingLeft + mPaddingRight;
int widthSize = mTotalLength;
int widthSizeAndState = resolveSizeAndState(widthSize, widthMeasureSpec, 0);
widthSize = widthSizeAndState & MEASURED_SIZE_MASK;
maxHeight += mPaddingTop + mPaddingBottom;
// Check against our minimum height
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK),
resolveSizeAndState(maxHeight, heightMeasureSpec,
(childState<<MEASURED_HEIGHT_STATE_SHIFT)));
}
测量流程:
总结
- 对于 View 来说,其 MeasureSpec 是由其父容器 ViewGroup 的 MeasureSpec 和 View 自身的 LayoutParams 来共同决定的。
- 对于DecorView,其MearsureSpec是窗口尺寸和其自己的LayoutParames共同决定。
最后
以上就是落寞中心为你收集整理的Android 自定义控件基础:measure过程的全部内容,希望文章能够帮你解决Android 自定义控件基础:measure过程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复