Android自定义控件——View的测量模式

Android自定义控件——View的测量模式

  • 简介
  • View的测量模式
  • 应用

简介

上一篇写了自定义控件的入门,自定义TextView,里面涉及到了View的测量模式,在里面只是做了简单说明。这篇做一个详细的说明。建议大家两篇结合着看。Android自定义View入门
一个Android开发者总会遇到自定义控件的问题。要学会自定义控件的开发,最好的方法是将要用到的知识点一个个掌握。当掌握这些分散的知识点就意味着写一个自定义控件会变得容易。本篇文章是对View的测量的探究。

View的测量模式

View的测量模式:通过源码我们知道View有三种测量模式。

1、MeasureSpec.AT_MOST : 在布局中指定了wrap_content
2、MeasureSpec.EXACTLY : 在布居中指定了确切的值 100dp match_parent fill_parent
3、MeasureSpec.UNSPECIFIED : 尽可能的大,很少能用到,ListView , ScrollView 在测量子布局的时候会用UNSPECIFIED

系统源码:

/**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

测试验证

上面已经说了这三者的区别,那么我们现在就来做一个验证

新建一个View。在onMesure()中写测量的代码。

public class MyTestView extends View {


    public MyTestView(Context context) {
        this(context,null);
    }

    public MyTestView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyTestView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        switch (MeasureSpec.getMode(widthMeasureSpec)){
            case AT_MOST:
                Log.e("View的测量模式","测量模式:AT_MOST   测量实际大小width:"+width);
                break;
            case MeasureSpec.EXACTLY:
                Log.e("View的测量模式","测量模式:EXACTLY   测量实际大小width:"+width);
                break;
            case MeasureSpec.UNSPECIFIED:
                Log.e("View的测量模式","测量模式:UNSPECIFIED   测量实际大小width:"+width);
                break;
        }

        switch (MeasureSpec.getMode(heightMeasureSpec)){
            case AT_MOST:
                Log.e("View的测量模式","测量模式:AT_MOST   测量实际大小height:"+height);
                break;
            case MeasureSpec.EXACTLY:
                Log.e("View的测量模式","测量模式:EXACTLY   测量实际大小height:"+height);
                break;
            case MeasureSpec.UNSPECIFIED:
                Log.e("View的测量模式","测量模式:UNSPECIFIED   测量实际大小height:"+height);
                break;
        }

        setMeasuredDimension(width,height);
    }
}

1、EXACTLY

第一种情况:设置MyTestView 为300dp(固定宽高度)

<com.wn.view02.MyTestView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/colorPrimary"/>

Log日志:
这里写图片描述

分析Log:
1、测量模式为EXACTLY
2、获取到的width和height都为 300. (系统测量会将单位转为px)

第二种情况:设置MyTestView 为match_parent

<com.wn.view02.MyTestView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary"/>

Log日志:
这里写图片描述

分析Log:
1、测量模式为EXACTLY
2、获取到的width为984和height都为1590. (系统测量会将单位转为px)

说明:说明两种测量模式都是EXACTLY ,MeasureSpec.EXACTLY : 在布居中指定了确切的值 100dp match_parent

1、AT_MOST

第三种情况:
a、父布局将layout_width,layout_height 都设为 match_parent
b、将子布局的layout_width,layout_height 都设为 wrap_content

<LinearLayout
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wn.view02.MainActivity">

    <com.wn.view02.MyTestView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"/>
LinearLayout>

Log日志:

这里写图片描述

分析:
子布局的宽高测量模式都为: AT_MOST

父布局的layout_width和layout_height都为match_parent,父布局的宽高约为屏幕的宽高。

子布局的layout_width和layout_height都为wrap_content,子布局大小不固定,但是最大值受父布局大小影响。这种情况的测量模式就是 AT_MOST 。

第四种情况:
a、父布局将layout_width,layout_height 都设为 100dp
b、将子布局的layout_width,layout_height 都设为 wrap_content

<LinearLayout
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="300dp"
    android:layout_height="300dp"
    tools:context="com.wn.view02.MainActivity">

    <com.wn.view02.MyTestView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"/>
LinearLayout>

Log日志:
这里写图片描述

分析:
子布局大小不固定,但是最大值受父布局大小影响。这种情况的测量模式就是 EXACTLY 。

测试出一种特殊的情况

当父布局是RelativeLayout,子布局的layout_width,layout_height 都设为 wrap_content时,子布局的width测量模式为EXACTLY

<RelativeLayout
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="100dp"
    android:layout_height="100dp"
    tools:context="com.wn.view02.MainActivity">

    <com.wn.view02.MyTestView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"/>
RelativeLayout>

Log日志:
这里写图片描述

分析:

我暂时也不知道子View的宽的测量模式是EXACTLY。这应该是一种特殊情况。

这里再次做提醒:如果这个View的测量模式为AT_MOST,这个View一定设置了wrap_content

3、UNSPECIFIED

"http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    "match_parent"
        android:layout_height="match_parent">

    "wrap_content"
        android:layout_height="wrap_content"
        android:background="#33ee33"/>
    

分析:

这里我们只要分析height就行了,这种情况下 父布局ScrollView的子view的高度是不固定的,想要多大就可多大。所以这里height的测量模式为 UNSPECIFIED.

应用

1、先测量再绘制

在写自定义控件时,涉及到测量绘制的。一般是先测量再绘制。

2、测量方法

上面代码已贴出

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

        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        switch (MeasureSpec.getMode(widthMeasureSpec)){
            case AT_MOST:
                Log.e("View的测量模式","测量模式:AT_MOST   测量实际大小width:"+width);
                break;
            case MeasureSpec.EXACTLY:
                Log.e("View的测量模式","测量模式:EXACTLY   测量实际大小width:"+width);
                break;
            case MeasureSpec.UNSPECIFIED:
                Log.e("View的测量模式","测量模式:UNSPECIFIED   测量实际大小width:"+width);
                break;
        }

        switch (MeasureSpec.getMode(heightMeasureSpec)){
            case AT_MOST:
                Log.e("View的测量模式","测量模式:AT_MOST   测量实际大小height:"+height);
                break;
            case MeasureSpec.EXACTLY:
                Log.e("View的测量模式","测量模式:EXACTLY   测量实际大小height:"+height);
                break;
            case MeasureSpec.UNSPECIFIED:
                Log.e("View的测量模式","测量模式:UNSPECIFIED   测量实际大小height:"+height);
                break;
        }

        setMeasuredDimension(width,height);
    }

3、测量完毕之后一定要调用setMeasuredDimension(width, height);
要调用setMeasuredDimension或者super.onMeasure来设置自身的mMeasuredWidth和mMeasuredHeight,否则,就会抛出异常.

你可能感兴趣的:(自定义View)