测量控件尺寸(宽度、高度)是开发自定义控件的第一步,只有确定尺寸后才能开始画(利用canvas在画布上画,我们所使用的控件实际上都是这样画上去的)。当然,这个尺寸是需要根据控件的各个部分计算出来的,比如:padding、文字大小,间距等。
非容器控件的onMeasure
下面我们就来看看如何给非容器控件(即直接extends View)这只尺寸的:
1.
@Override
2.
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
3.
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
4.
}
通过重写onMeasure()方法来设置尺寸。这个方法实际是由容器控件(LinearLayout、RelativeLayout)来调用的,以便容器控件知道我需要分配给你多大空间或尺寸。
计算完尺寸后,最终调用setMeasuredDimension()来设置。比如,我要创建个 宽度是200px,高度是100px的控件,就可以setMeasuredDimension(200, 100)。
注意setMeasuredDimension()接收的值是px值,而不是dp值,所以我们不可能像上面那样将尺寸写固定的。如何做那:
参数 int widthMeasureSpec, int heightMeasureSpec 是指明控件可获得的空间以及关于这个空间描述的元数据,是与布局文件中android:layout_width、android:layout_height相联系的。如何使用:
01.
/**
02.
* 计算组件宽度
03.
*/
04.
private
int
measureWidth(
int
widthMeasureSpec) {
05.
int
result;
06.
int
specMode = MeasureSpec.getMode(widthMeasureSpec);
07.
int
specSize = MeasureSpec.getSize(widthMeasureSpec);
08.
09.
if
(specMode == MeasureSpec.EXACTLY) {
//精确模式
10.
result = specSize;
11.
}
else
{
12.
result = getDefaultWidth();//最大尺寸模式,getDefaultWidth方法需要我们根据控件实际需要自己实现
13.
if
(specMode == MeasureSpec.AT_MOST) {
14.
result = Math.min(result, specSize);
15.
}
16.
}
17.
return
result;
18.
}
调用MeasureSpec.getMode(measureSpec),可以获得设置尺寸的mode(模式)。
mode共有三种情况,取值分别为:
MeasureSpec.UNSPECIFIED,MeasureSpec.EXACTLY,MeasureSpec.AT_MOST。
MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。
MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。
measureWidth() 方法是个固定实现,几乎不用改, 唯一需要我们实现的就是getDefaultWidth()。即,当mode处于最大模式下,此时父容器会将他所能给你的或者说目前剩余的最大空间给你。你只能在这个空间内设定控件尺寸。一个简单的类似TextView的getDefaultWidth()的实现:
1.
private
void
getDefaultWidth(){
2.
int
txtWidth = (
int
)
this
.paint.measureText(
this
.text);
3.
return
txtWidth +
this
.paddingLeft +
this
.paddingRight;
4.
}
上面说了宽度的测量,高度同理:
01.
/**
02.
* 计算组件高度
03.
*/
04.
private
int
measureHeight(
int
measureSpec) {
05.
int
result;
06.
int
specMode = MeasureSpec.getMode(measureSpec);
07.
int
specSize = MeasureSpec.getSize(measureSpec);
08.
09.
if
(specMode == MeasureSpec.EXACTLY) {
10.
result = specSize;
11.
}
else
{
12.
result = getDefaultHeight();
13.
if
(specMode == MeasureSpec.AT_MOST) {
14.
result = Math.min(result, specSize);
15.
}
16.
}
17.
return
result;
18.
}
有了上面的实现,我们就可以在布局文件中使用控件时,通过android:layout_width、android:layout_height来自定义控件的宽度、高度。
容器控件的onMeasure
容器控件一般是继承ViewGroup,ViewGroup是个抽象类,本身没有实现onMeasure,但是他的子类都有各自的实现,通常他们都是通过measureChildWithMargins函数或者其他类似于measureChild的函数来遍历测量子View,被GONE的子View将不参与测量,当所有的子View都测量完毕后,才根据父View传递过来的模式和大小来最终决定自身的大小.
在测量子View时,会先获取子View的LayoutParams,从中取出宽高,如果是大于0,将会以精确的模式加上其值组合成MeasureSpec传递子View,如果是小于0,将会把自身的大小或者剩余的大小传递给子View,其模式判定在前面表中有对应关系.
ViewGroup一般都在测量完所有子View后才会调用setMeasuredDimension()设置自身大小。
==========================================================================================
欢迎加入我们的技术交流群:
Android群: 66756039
JavaEE群: 361579846