上一篇写了自定义控件的入门,自定义TextView,里面涉及到了View的测量模式,在里面只是做了简单说明。这篇做一个详细的说明。建议大家两篇结合着看。Android自定义View入门
一个Android开发者总会遇到自定义控件的问题。要学会自定义控件的开发,最好的方法是将要用到的知识点一个个掌握。当掌握这些分散的知识点就意味着写一个自定义控件会变得容易。本篇文章是对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);
}
}
第一种情况:设置MyTestView 为300dp(固定宽高度)
<com.wn.view02.MyTestView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorPrimary"/>
分析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:
1、测量模式为EXACTLY
2、获取到的width为984和height都为1590. (系统测量会将单位转为px)
说明:说明两种测量模式都是EXACTLY ,MeasureSpec.EXACTLY : 在布居中指定了确切的值 100dp match_parent
第三种情况:
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>
分析:
子布局大小不固定,但是最大值受父布局大小影响。这种情况的测量模式就是 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>
分析:
我暂时也不知道子View的宽的测量模式是EXACTLY。这应该是一种特殊情况。
这里再次做提醒:如果这个View的测量模式为AT_MOST,这个View一定设置了wrap_content
"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,否则,就会抛出异常.