当我们要进行自定义view时,在调用 onDraw绘图前,肯定要进行写预处理操作,从而获得我们自定义view的布局大小,变得很重要,这里onMeasure函数能帮助到你,但是这个函数有些不好理解。在进行分析前,我们下GOOGLE下,了解下一下概念:
实现onMeasure()方法基本需要完成下面三个方面的事情:
1.传递进来的参数,widthMeasureSpec,和heightMeasureSpec是你对你应该得出来的测量值的限制.
The overidden onMeasure() method is called with width and height measure specifications(widthMeasureSpec and heightMeasureSpec parameters,both are integer codes representing dimensions) which should be treated as requirements for the restrictions on the width and height measurements you should produce.2. 你在onMeasure计算出来设置的width和height将被用来渲染组件.应当尽量在传递进来的width和height 声明之间.
虽然你也可以选择你设置的尺寸超过传递进来的声明.但是这样的话,父容器可以选择,如clipping,scrolling,或者抛出异常,或者(也许是用新的声明参数)再次调用onMeasure()
Your component's onMeasure() method should calculate a measurement width and height which will be required to render the component.it should try to stay within the specified passed in.although it can choose to exceed them(in this case,the parent can choose what to do,including clipping,scrolling,throwing an excption,or asking the onMeasure to try again,perhaps with different measurement specifications).3.一但width和height计算好了,就应该调用View.setMeasuredDimension(int width,int height)方法,否则将导致抛出异常.
Once the width and height are calculated,the setMeasureDimension(int width,int height) method must be called with the calculated measurements.Failure to do this will result in an exceptiion being thrown
简而言之,onMeasure能根据你在布局中对view布局属性(layout_width:layout_height)的设置,从而得出widthMeasureSpec, heightMeasureSpec这两个限制值,由这两个限制值调用 MeasureSpec.getSize 或MeasureSpec.getMode 能得到要绘制的view的布局的大小和模式。
查看GOOGLE文档 布局的模式有下面三种:
(1)UPSPECIFIED :父容器对于子容器没有任何限制,子容器想要多大就多大.UNSPECIFIED The parent has not imposed any constraint on the child.It can be whatever size it wants(2) EXACTLY父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间.
EXACTLY The parent has determined and exact size for the child.The child is going to be given those bounds regardless of how big it wants to be.(3) AT_MOST子容器可以是声明大小内的任意大小.
AT_MOST The child can be as large as it wants up to the specified size下面我们通过实例分析下:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); Log.i(TAG,widthMeasureSpec+":"+heightMeasureSpec); int w = getMeasureWidth(widthMeasureSpec); int h = getMeasureHeight(heightMeasureSpec); setMeasuredDimension(w, h); //必须调用此方法,否则会抛出异常 } private int getMeasureHeight(int heightMeasureSpec) { int result = 0; int size = MeasureSpec.getSize(heightMeasureSpec); //每次调用此方法,测量用到的size会发生变化 int mode = MeasureSpec.getMode(heightMeasureSpec); //根据定义的Layout_width,Layout_height,会对此值产生影响 if (mode == MeasureSpec.EXACTLY) { //EXACTLY=1073741824 (0x40000000) 父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间. result = size; Log.i(TAG,"Height mode:" + "MeasureSpec.EXACTLY"); } else if (mode == MeasureSpec.UNSPECIFIED) { //UNSPECIFIED=0 (0x00000000) :父容器对于子容器没有任何限制,子容器想要多大就多大. result = (int) mPaint.measureText("") + getPaddingLeft() + getPaddingRight(); Log.i(TAG,"Height mode:" + "MeasureSpec.UNSPECIFIED"); } else if (mode == MeasureSpec.AT_MOST) { //AT_MOST=-2147483648 (0x80000000) :子容器可以是声明大小内的任意大小 result = Math.min(result, size); Log.i(TAG,"Height mode:" + "MeasureSpec.AT_MOST"); } Log.i(TAG,"Height size:" + size); return result; } private int getMeasureWidth(int widthMeasureSpec) { int result = 0; int size = MeasureSpec.getSize(widthMeasureSpec); int mode = MeasureSpec.getMode(widthMeasureSpec); if (mode == MeasureSpec.EXACTLY) { result = size; Log.i(TAG,"Width mode:" + "MeasureSpec.EXACTLY"); } else if (mode == MeasureSpec.UNSPECIFIED) { result = (int) mPaint.measureText("") + getPaddingTop() + getPaddingBottom(); Log.i(TAG,"Width mode:" + "MeasureSpec.UNSPECIFIED"); } else if (mode == MeasureSpec.AT_MOST) { result = Math.min(result, size); Log.i(TAG,"Width mode:" + "MeasureSpec.AT_MOST"); } Log.i(TAG,"Width size:" + size); return result; }
当我们view的布局为:
android:layout_width="match_parent"
android:layout_height="match_parent"
打印信息如下: 我这里的屏幕分辨率为(w:480, h800)
由上可知(宽高度能占整个父控件的布局大小,单不能超出父控件,h 为690,因为还有标题栏和系统状态栏占用,模式是EXACTLY:父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间.)I/myview (31274): Width mode:MeasureSpec.EXACTLY I/myview (31274): Width size:480 I/myview (31274): Height mode:MeasureSpec.EXACTLY I/myview (31274): Height size:690 I/myview (31274): 1073742304:1073742514 I/myview (31274): Width mode:MeasureSpec.EXACTLY I/myview (31274): Width size:480 I/myview (31274): Height mode:MeasureSpec.EXACTLY I/myview (31274): Height size:690
当我们view布局为:
android:layout_width="wrap_content"
android:layout_height="wrap_content"
由上可知(得到widthMeasureSpec, heightMeasureSpec的值是父控件的最大值,但模式是AT_MOST:子容器可以是声明大小内的任意大小.,我们可以通过setMeasuredDimension(w, h);设置自己想要的比父控件布局更大的值,这时会显示不完全)I/myview ( 1067): -2147483168:-2147482958 I/myview ( 1067): Width mode:MeasureSpec.AT_MOST I/myview ( 1067): Width size:480 I/myview ( 1067): Height mode:MeasureSpec.AT_MOST I/myview ( 1067): Height size:690 I/myview ( 1067): onDraw: w:600 h:800 I/myview ( 1067): -2147483168:-2147482958 I/myview ( 1067): Width mode:MeasureSpec.AT_MOST I/myview ( 1067): Width size:480 I/myview ( 1067): Height mode:MeasureSpec.AT_MOST I/myview ( 1067): Height size:690
当我们view布局为:
android:layout_width=“800dp"
android:layout_height="800dp"
由上可知(此时得到了h1200 w1200的布局值,而 模式还是EXACTLY。不懂??)I/myview ( 1381): 1073743024:1073743024 I/myview ( 1381): Width mode:MeasureSpec.EXACTLY I/myview ( 1381): Width size:1200 I/myview ( 1381): Height mode:MeasureSpec.EXACTLY I/myview ( 1381): Height size:1200 I/myview ( 1381): onDraw: w:1200 h:1200 I/myview ( 1381): 1073743024:1073743024 I/myview ( 1381): Width mode:MeasureSpec.EXACTLY I/myview ( 1381): Width size:1200 I/myview ( 1381): Height mode:MeasureSpec.EXACTLY I/myview ( 1381): Height size:1200