欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Android自定义控件系列一:如何测量控件尺寸,android控件,当然,这个尺寸是需要根据

来源: 开发者 投稿于  被查看 29802 次 评论:12

Android自定义控件系列一:如何测量控件尺寸,android控件,当然,这个尺寸是需要根据


测量控件尺寸(宽度、高度)是开发自定义控件的第一步,只有确定尺寸后才能开始画(利用canvas在画布上画,我们所使用的控件实际上都是这样画上去的)。当然,这个尺寸是需要根据控件的各个部分计算出来的,比如:padding、文字大小,间距等。


非容器控件的onMeasure

下面我们就来看看如何给非容器控件(即直接extends View)这只尺寸的:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}

通过重写onMeasure()方法来设置尺寸。这个方法实际是由容器控件(LinearLayout、RelativeLayout)来调用的,以便容器控件知道我需要分配给你多大空间或尺寸。

计算完尺寸后,最终调用setMeasuredDimension()来设置。比如,我要创建个 宽度是200px,高度是100px的控件,就可以setMeasuredDimension(200, 100)。

注意setMeasuredDimension()接收的值是px值,而不是dp值,所以我们不可能像上面那样将尺寸写固定的。如何做那:

参数 int widthMeasureSpec, int heightMeasureSpec 是指明控件可获得的空间以及关于这个空间描述的元数据,是与布局文件中android:layout_width、android:layout_height相联系的。如何使用:

/**
     * 计算组件宽度
     */
    private int measureWidth(int widthMeasureSpec) {
        int result;
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);

        if (specMode == MeasureSpec.EXACTLY) {//精确模式
            result = specSize;
        } else {
        	result = getDefaultWidth();//最大尺寸模式,getDefaultWidth方法需要我们根据控件实际需要自己实现
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

调用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()的实现:

private void getDefaultWidth(){
  int txtWidth = (int)this.paint.measureText(this.text);
  return txtWidth + this.paddingLeft + this.paddingRight;
}


上面说了宽度的测量,高度同理:

/**
     * 计算组件高度
     */
    private int measureHeight(int measureSpec) {
        int result;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = getDefaultHeight();
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

有了上面的实现,我们就可以在布局文件中使用控件时,通过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()设置自身大小。

有兴趣的可以看下LinearLayout、RelativeLayout的源码。

另外,可以参考http://www.see-source.com/androidwidget/list.html中的控件,这些都是实际中最有可能用到的。


用户评论