Called to determine the size requirements for this view and all of its children
onMeasure(),是对View 进行测量的,因为只有知道View的大小才能,进行正确的onDraw();当然继承于ViewGroup的View还要搞一下onLayout()这个方法。
-
Override
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
简单的两个参数,widthMeasureSpec和heightMeasureSpec,点进去看super的代码也很容易看懂:
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
再追下去的代码也很简单,能看的懂就是分类设置了一下宽高,这里不贴了。
看一下我们经常用的写法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize,heightSize);
}
常规这样其实也就满足了 当我们写成layout_width/layout_height等于match_parent 的时候。那么问题来了,当我们写成wrap_content的时候那么宽高怎么设置呢?不会写砸门来借鉴,网上有一大堆怎么处理wrap_content的,不知道对错怎么办,找个权威一点的研究一下吧,正常些代码会经常用到TextView,这是google官方的控件,TextView很具有代表性了,自己会随着字数的增加变长变高,我们点进去看看他是怎么实现的:
if (widthMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
width = widthSize;
} else {
//省去很多代码,这里只看怎么设置长度的
if (boring == null || boring == UNKNOWN_BORING) {
if (des < 0) {
des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
mTransformed.length(), mTextPaint, mTextDir, widthLimit));
}
width = des;
} else {
width = boring.width;
}
//看看背景的宽高
final Drawables dr = mDrawables;
if (dr != null) {
width = Math.max(width, dr.mDrawableWidthTop);
width = Math.max(width, dr.mDrawableWidthBottom);
}
/ ** if (mHint != null) {
int hintDes = -1;
int hintWidth;
if (mHintLayout != null && mEllipsize == null) {
hintDes = desired(mHintLayout);
}
if (hintDes < 0) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
}
if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
if (hintDes < 0) {
hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
mHint.length(), mTextPaint, mTextDir, widthLimit));
}
hintWidth = hintDes;
} else {
hintWidth = hintBoring.width;
}
if (hintWidth > width) {
width = hintWidth;
}
}
**/ 这一段讲的是hint的宽度 上面一句当hint里面的字大于文本的宽度的时候,按照hint的宽度来。
width += getCompoundPaddingLeft() + getCompoundPaddingRight();
//***这个EMS 是 maxEms属性,最大多少个,有的话就按照最少的来显示***
if (mMaxWidthMode == EMS) {
width = Math.min(width, mMaxWidth * getLineHeight());
} else {
width = Math.min(width, mMaxWidth);
}
if (mMinWidthMode == EMS) {
width = Math.max(width, mMinWidth * getLineHeight());
} else {
width = Math.max(width, mMinWidth);
}
// Check against our minimum width
width = Math.max(width, getSuggestedMinimumWidth());
//这里看是不是wrap_content属性。 然后跟刚开始测量出来的比。
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
}
这个TextView中的onMeasure()方法真是写了一大坨,主要是干什么的这里简单总结一下大体思路。
-
当widthMode == MeasureSpec.EXACTLY时候
根据注释可以知道 //Parent has told us how big to be. So be it.,给的多大就是多大。 -
widthMode == MeasureSpec.AT_MOST
- 首先检验出多少个字,然后根据字体来测量出宽度。
- 看一下有没有背景Drawable ,有背景图片就再比较一下,字体与图片中选大的设置。
- 最后排查一下是不是有设置每行最大多少个字。
- 最后看一下跟原本测出来的进行比较,取小一点的
-
等都不等于的时候,也就是我们的 MeasureSpec.UNSPECIFIED
width = Math.max(width, getSuggestedMinimumWidth());这是最后一个设置宽度的代码,追一下:protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
再搞了一波背景的事情。但是一般这个方法不怎么用,我们自定义的时候。
所以一般时候我们设置只有两种模式,一种是EXACTLY这种我们直接给上match或者固定dp拿到的值,不做任何处理的情况下我们可以直接一把梭进setMeasuredDimension,当然有时候我们要搞一下事情也是可以得,比如你要画一个圆,那最好宽高一样,自己都取出来之后取min就可以。另一种是ATMOST,这个就是wrap_content的产物,自己要写好计算方式,动态计算填补宽高。至于另外一种其实也不是没用,可以帮我们解决一些问题。
比如ListView 嵌套在ScorllView中的时候,高度不对。
看一下ListView中的onMeasure():
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
这个就是只显示一个的问题。heightSize只显示了一个 那反过来推 是不是heightMode == MeasureSpec.UNSPECIFIED造成的,网上搜一下解决方法:
/**
* 重写该方法,达到使ListView适应ScrollView的效果
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
知道了造成问题的点还有解决方案,我们再去追一下这个问题。那就去看看ScrollView 中measureChild()方法:
childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
MeasureSpec.UNSPECIFIED);
这里直接给 下面的字View安排了MeasureSpec.UNSPECIFIED正好给ListView了导致显示只有一个。
我们看一下RecyclerView,发现并不会导致问题,结合onMeasure看一下,RecyclerView没有那么多的问题。
实际开发中还有一些问题,比如onMeasure会被执行多次,这时候假如我们用List来进行记录的时候一定要记得在measure中给初始化一下。这里查看资料大部分都是说因为父控件对子控件测量的时候不是太满意就重新测量了几次。这里的流程是 performTraversals()---performMeasure()--measure()--onMeasure() 来执行测量的,ViewRootImpl中:
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
这个measureAgain 就决定了是不是要继续测量。看一下上面的代码:
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
具体就是测量的不爽,也没有好好分析就是通过这边的代码来看了一下这个问题。
最后关于获取控件的宽高,create,start resume的时候没有办法保证完全可以获取控件的宽高(待补充讲解),Activity中有个onWindowFocusChanged代表View初始化好了这时候可以进行获取。还有就是view.post(runnable)。
关于ViewGroup的onMeasure()结合空间来记录吧。
网友评论