在自定义view中,我们会遇到最重要的三个方法。OnMeasure,OnLayout OnDraw。OnLayout决定了在ViewGroup中的位置。 OnDraw决定了如何绘制这个view。而在这里要介绍的OnMeasure决定了View的大小。
先来看下TextView中的OnMeasure方法:
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- int width;
- int height;
- ...
- if (widthMode == MeasureSpec.EXACTLY) {
- // Parent has told us how big to be. So be it.
- width = widthSize;
- } else {
- if (mLayout != null && mEllipsize == null) {
- des = desired(mLayout);
- }
- ...
- setMeasuredDimension(width, height);
首先要先明白是谁调用了onMeasure()这个方法。 调用它的是这个view的父视图。widthMeasureSpec和heightMeasureSpec这两个参数由其父视图viewGroup的layout_height,layout_width,padding以及本身的layout_margin,layout_width,layout_height共同决定(其实还与widght权重有关,但是这个比较复杂先忽略它)。
接着要来说一下widthMeasureSpec的组成结构,它包含了两部分,Mode and Size. 你可以会奇怪为啥一个Int能包含两部分信息。 其实它是一个32位的,高两位用于表示Model,低30位表示Size。
Model由本身view和Viewgroup的layout_width有关,Size与ViewGroup中得Padding,本身的margin,layout_width。
先来说说Model,它有三种状态:
exactly精确状态01:MeasureSpec.EXACTLY:view就为本身指定的大小,再xml中定义的为MATCH_PARENT时model对应于EXACTLY
at_most最大状态00:MeasureSpec.AT_MOST:view的大小不得大于VIewGroup的大小,再xml中定义的为WRAP_CONTENT对应为于AT_MOST
unspecified为指明状态10:MeasureSpec.UNSPECIFIED: view的大小不受限制,当view设置为制定大小时情况对应这种
32位的数字解析出来稍微有点麻烦,所以Google官方也提供了几个方法来获取Model 和Size——MeasureSpec.getModel和MeasureSpec.getSize。
如下的代码是一个列子,注意了setMeasuredDimension(width,height)这里的高宽才是最后真真的大小。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Log.e("Measure",MeasureSpec.toString(widthMeasureSpec));
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
float textWidth = mBound.width();
int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
width = desired;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
float textHeight = mBound.height();
int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
height = desired;
}
setMeasuredDimension(width, height);
}
在这里顺便提一下一种让listview或者gridview取消滚动栏方法的实现原理
int expandSpec= MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE,MEASURESPEC.AT_MOST);
super.onMeasure(widthMeasureSpec,expandSpec)
makeMeasureSpec这个方法是传入size和model返回一个MeasureSpec值。还记得前面说的吗,高两位是model,低位是size,这里我们把最大的整形左移两位空出model的值,剩下的size部分就是30位的最大值了,即size为最大;然后我们又让model设置为AT_MOST即wrap_content有多大显示多大,这样view的高度就成了view本身有多大就显示多大,就不会出现自带的滚动条了~