您的位置:首页 > 移动开发 > Android开发

Android View的measure过程详解

2017-03-05 23:38 501 查看

注意

阅读本文至少需要先了解MeasureSpec的工作原理,可以参见网上其他相关博客,本文不做解释。

measure介绍

顾名思义,measure方法用于测量View的大小,由View所在的ViewGroup调用:

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);


其中的 childWidthMeasureSpec 和 childHeightMeasureSpec 参数一般受ViewGroup的MeasureSpec及children的LayoutParam影响。

下面主要分两部分介绍该过程。

View的Measure过程

View的measure为final方法,其中会调用onMeasure方法,如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}


此处涉及四个方法:setMeasuredDimension,getDefaultSize,getSuggestedMinimumWidth,getSuggestedMinimumHeight下面一一介绍:

其中的setMeasureDimension方法会设置View的测量值,此处的测量值可以大致理解为View的最终大小,之所以这么说是因为View的最终大小是在layout过程中确定的,但他们基本上是相同的。

getMeasuredHeight();//measure中确定的高度
getHeight();//View的最终高度
getMeasuredWidth();//measure中确定的宽度
getWidth();//View的最终宽度


getDefaultSize方法如下:

/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
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;
}


注释已经写的很明确了,该方法接受两个参数,第一个参数size为View的默认大小,第二个参数为父ViewGroup传过来的大小,然后在MeasureSpec的约束下获取到View的最终大小。

可以看到,View的SpecMode无论是AT_MOST还是EXACTLY,最终结果都是SpecSize。也就是说,View默认情况下,match_parent与wrap_content的值相等,也就是父布局的剩余大小最大值。

所以当我们自定义View时需要注意,如果需要让View支持wrap_content就需要重写onMeasure方法。

getSuggestedMinimumWidth与getSuggestedMinimumHeight方法工作原理相同,这里只介绍getSuggestedMinimumWidth方法:

/**
* Returns the suggested minimum width that the view should use. This
* returns the maximum of the view's minimum width)
* and the background's minimum width
*  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned width is within the requirements of the parent.
*
* @return The suggested minimum width of the view.
*/
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}


方法很简答,如果该View有背景则返回背景与mMinWidth的最大值,如果没有背景就返回mMinWidth。其中的mMinWidth通过setMinimumWidth或者android:minWidth指定。

ViewGroup的Measure过程:

ViewGroup除了会对自身measure外,还会对所有的子View进行measure。

ViewGroup是一个抽象类,其中并没有重写onMeasure方法,所以不同的ViewGroup中对onMeasure的重写方法都有所不同。

但是ViewGroup中给我们提供了一个用于measure所有子View的方法,我们可以使用此方法measure,也可以自己写:

/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/
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(child, widthMeasureSpec, heightMeasureSpec);
}
}
}


参数为当前ViewGroup的widthMeasureSpec和heightMeasureSpec。

此方法很简单,遍历所有子View,如果View不为GONE,就对其进行measure,measureChild(child, widthMeasureSpec, heightMeasureSpec)方法如下:

/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
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);
}


先获取子View的LayoutParam及ViewGroup的padding,然后通过getChildMeasureSpec方法获取到大小,重点在于getChildMeasureSpec方法:

/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
*        margins, if applicable
* @param childDimension How big the child wants to be in the current
*        dimension
* @return a MeasureSpec integer for the child
*/
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;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}


上面的代码逻辑很清晰,主要是通过ViewGroup的specMode,specSize及View的LayoutParam获得View的MeasureSpec。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android