Android View 的测量

在绘制View之前,我们还需要考虑一下,系统是如何绘制这些View的。相信大家都曾经玩过这样一个游戏:一个人蒙着眼睛,那笔去画板上画出一个指定的图案,另一个人则通过说话来指导他如何去画。比如你会指导他,在距画板边缘一掌宽的地方画一个边长大概10厘米的正方形,而如果你只告诉他,画一个矩形,那么你的同伴就无法准确的画出这个图形了。其实,Android就好像那么蒙着眼睛画画的人,你必须精确的告诉他该如何去画,它才能绘制出你想要的图形。

在显示生活中,如果我们想要去画一个图形,就必须知道它的大小和位置。同样Android系统在绘制View前,也必须对View进行测量,即告诉系统画出多大的View。这个过程在onMeasure()方法中进行。

Android系统给我们提供了一个设计短小精悍却功能强大的类——MeasureSpec类,通过它来帮助我们测量View。MeasureSpec是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小,在计算中使用位运算的原因是为了提高并优化效率。

测量的模式可以为以下三种。

● EXACTLY

即精准值模式,当我们将控件的layout_width属性或layout_height属性指定为具体数字时,比如android:layout_width="100dp",或者指定为match_parent属性时,(占据父View的大小),系统使用的事EXACTLY模式。

● AT_MOST

即最大值模式,当控件的layout_width属性或者layout_height属性指定为wrap_content时,控件大小一般随着控件的子空间或内容的变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。

● UNSPECIFIED

这个属性比较奇怪——它不指定其大小测量模式,View想多大就多大,通常情况下在绘制自定义View时才会使用。

View类默认的onMeasure()方法只支持EXACTLY模式,所以如果在自定义控件的时候不重写onMeasurs方法的话,就只能使用EXACTLY模式。控件可以响应你指定的具体宽高值或者是match_parent属性。而如果要让自定义View支持wrap_content属性,那么就必须重写onMeasurs()方法来指定wrap_content是的大小。

通过MeasureSpec这一个类,我们就获取了View的测量模式和View想要绘制的大小。有了这些信息,我们就可以控制View最后显示的大小。

下面我们就来看一个简单的实例,演示如何进行View的测量。首先,要重写onMeasue()方法,该方法如下所示。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

在IDE中按住Ctrl键查看super.onMeasure()方法。可以发现,系统最终会调用setMeasuredDinmension(int measuredWidth,int measuredHeight)方法将测量后的宽高值设置进去,从而完成测量工作。所以在重写onMeasure()方法后,最终要做的工作就是把测量后的宽高值作为参数传给setMeasuredDimension()方法。

通过上面的分析,重写onMeasure()方法代码如下所示。

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

 

在onMeasure()方法中,我们调用了measureWidth()方法和measureHeigth()方法,分布对宽高进行重新定义,参数则是宽和高MeasureSpec对象,MeasureSpec对象中包含了测量的模式和测量值的大小。

下面我们就以measureWidth()方法为例,讲解如何自定义测量值。

第一步,从MeasureSpec对象中提取出具体的测量模式和大小,代码如此所示。

    int specMode = MeasureSpec.getMode(measureSpec);
    int specSizw = MeasureSpec.getSize(measureSpec);

接下来,通过判断测量的模式,给出不同的测量值,当specMode为EXACTLY时,直接使用指定的specSize即可,当specMode为其他两种模式时,需要给它一个默认的大小。特别地,如果指定wrap_content属性,即AT_MOST模式,则需要取出我们指定的大小与specSize中最小的一个来作为最后的测量值,measureWidth()方法的代码如下所示。这段代码基本上也可以作为模板代码。

 private int measureWidth(int measureSpec){
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSizw = MeasureSpec.getSize(measureSpec);
        if (specMode==MeasureSpec.EXACTLY){
            result = specSizw;
        }else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST){
                result = Math.min(result,specSizw);
            }
        }
        return result;
    }

measureHeigth()方法与measureWidth()基本一致,这里就不在给出代码了,通过这两个方法,我们就完成了对宽高值的自定义。最后,可以在程序中验证以上的分析。

当布局文件中,先指定确定的宽高值400px,程序运行效果如图(1)所示。

当指定宽高属性为match_parent属性时,程序运行效果如图(2)所示。

当指定宽高属性为wrap_content属性时,如果不重写onMeasure()方法,那么系统就不知道该使用默认多大的尺寸。因此,它就会默认填充整个父布局,所以重写onMeasure()方法的目的,就是为了能够给View一个wrap_content属性下的默认大小,程序运行效果如图(3)所示。

 

                                      

               (1)指定宽高值为400px                    (2)宽高属性为match_parnet              (3)宽高属性为wrap_content

可以发现,当指定wrap_content属性时,View就获得了一个默认值200px,而不是在填充父布局了。

通过这个小的实例,相信大家应该对View的测量不在陌生了,它并没有什么高深莫测的东西,它的整个过程与我们生活中精准绘图过程基本是一样的。

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_34368586/article/details/86604079
今日推荐