浅谈Android自定义VIew的测量

目前在阅读Android群英传的第三章自定义控件架构和自定义控件,准备写下自己的学习笔记和一些心得,增强自己.如果文章有错误的地方,请各位朋友指出.文章的内容有些参考书中的东西和自己的理解.
**在此,感谢作者 **


1.Android自定义控件架构

  • 对于书中讲的控件架构我是这样理解的,无论是View还是ViewGroup都是继承View的.
    View的架构应该是这样的ViewGroup里面可以包含ViewViewGroup.包含的ViewGroup也可以拥有ViewViewGroup.

    View树
  • 对于ActivityView的关系的理解,Activity中包含一个Window对象,书中说道是一般由PhoneWindow来实现.PhoneWindow中加载了一个DecorView,而DecorView包含TitleViewContentView,而这个ContentView就是我们常在Activity中调用的OnCreate()方法中setContentView(R.xx.xx)加载XML布局文件来实现的.这些并没有在代码中进行实践.

    Activity和View的关系

2. Android自定义控件的测量(View的测量)

  • 对于View的测量,就像你去画画,你总要知道你要在黑板上哪一块地方去画,画多大,然后在进行绘制.
    而在Android中,其实是一样的,你的View总要知道在那个布局中哪个地方显示,显示多大.而系统是需要对View进行测量的.测量是通过ViewonMeasure()方法来对View进行测量的.
  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
  • 在Android 中提供了MeasureSpec类来帮助我们对View进行测量.测量的模式分为三种:

  • EXACTLY(精准模式)
    该模式为精准模式,当你为View的layout_width或者layout_height指定固定的值或者为match_parent的时候为精准模式.

 代码示例:
  
  • AT_MOST(最大值模式)
    该模式为最大值模式,当控件的layout_width指定为wrap_content的时候,View会随着子View的大小而改变自身大小,比如一个LinearLayout的宽指定为wrap_content的时候,它的宽度就会随着子View的宽度大小而改变.或者根据View中的内容而改变自身大小,比如EditText的宽度设置为wrap_content的时候,文字越多,它的款的就会越长.但是一般都不要超过父View的宽度.

  • UNSPECIFIED
    该模式不是很理解.不指定View的测量模式,想多大就多大,通常情况用于自定义控件.

View类的onMeasure()方法默认只支持EXACTLY(精准模式),但是我们自定义的控件肯定要支持
layout_width=wrap_content,那么我们就要自己去写代码来支持AT_MOST模式.就需要在View中重写onMeasure()方法来实现.
为什么说View会默认支持EXACTLY,而灭有支持AT_MOST,我们先通过重写的onMearsure()方法中查看父类ViewonMearsure()方法,在super.onMearsure()中是这样实现的.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

查看父类View的代码我们明白最终调用 setMeasuredDimension( )来江测量的宽高设置进去,而完成测量工作.所以在我们测量完宽高后也需要调用setMeasuredDimension()来完成测量后的设置.那么,我们去查看getSuggestedMinimumWidth()方法的实现.

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

此方法只返回了一个int值,并没有看见对模式的修改,那么回来我们接着看getDefaultSize()方法中的实现

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

在这里我们发现,该方法初始化了一个resultint类型变量.并且通过MeasureSpec类的getmode()来获取测量模式和getSize()来获取测量后的值,然后进行switch.在这里我们发现有3个case,分别为MeasureSpec.UNSPECIFIED,直接将传过来的size直接返回.当caseMeasureSpec.AT_MOST的时候,我们发现什么都没有事实现,所以说View默认是不支持AT_MOST模式的.当caseMeasureSpec.EXACTLY的时候,是将测量后的值返回.

  • 实现自定义控件的onMearsure()
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     setMeasuredDimension(measureWidthOrHeight(widthMeasureSpec),measureWidthOrHeight(heightMeasureSpec));
    }
 private int measureWidthOrHeight(int measureSpec) {
        int result=0;
        //获取当前View的测量模式
        int mode = MeasureSpec.getMode(measureSpec);
        //精准模式获取当前Viwe测量后的值,如果是最大值模式,会获取父View的大小.
        int size = MeasureSpec.getSize(measureSpec);
        if (mode==MeasureSpec.EXACTLY){
            //当测量模式为精准模式,返回设定的值
            result=size;
        }else{
            //设置为WrapContent的默认大小
            result=50;
            if (mode==MeasureSpec.AT_MOST){
                //当模式为最大值的时候,默认大小和父类View的大小进行对比,返回最小的值
               result= Math.min(result,size);
            }
        }
        return result;
    }

onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法中,我将
super.onMeasure(int widthMeasureSpec, int heightMeasureSpec)删除,因为父类的方法中,直接进行了测量并将测量的值进行设置.在重写的onMearsure()方法中,我们需要对宽高重新测量并且支持AT_MOST模式,当然跟之前父类里面的实现原理是一样的,都是要调用setMeasuredDimension(int wdith,int height)设置测量后的值,只不过我们需要对宽高进行重新测量,重新测量的实现由measureWidthOrHeight(int measureSpec)实现.我们需要获取控件对应的测量模式,返回相对应的测量值.

你可能感兴趣的:(浅谈Android自定义VIew的测量)