要绘制一个view那么我们就需要告诉系统这个view的大小以及这个view绘制的位置。如何知道大小?必然是经过了测量才能知道view的大小。而这个测量的过程就是在onMeasure( )
方法中完成的。
说view的测量,MeasureSpec
这个类是怎么也无法跳过的。MeasureSpec
是一个32位的int值,其中int值的高两位为测量模式,低30位为测量的大小。
测量模式呢主要是下面的三种:
- EXACTLY模式
该模式为精确模式。什么时候用到的是这种模式呢?当我们将layout_width
orlayout_height
设置为具体数值的时候,如100dp
或者是match_parent
的时候。这时系统就是调用的精确模式 - AT_MOST
该模式为最大值模式。当我们将layout_width
orlayout_height
设置为wrap_content
的时候,系统调用的就是该模式。此时控件大小是随着子控件或者内容的变化而变化的,只要不超出父控件允许的最大范围即可 - UNSPECIFIED
这个模式一般只有在我们自定义控件的时候才会用到,它代表没有具体的测量模式,view自己想多大就多大。
三种模式中EXACTLY模式是onMeasure( )
方法中默认的测量模式。所以,我们在自定义控件的时候如果没有重写onMeasure( )
方法,那么我们就只能使用EXACTLY模式。那么我们在指定控件layout_width
or layout_height
的时候只能设置为具体的数值或者match_parent
,这时候如果想让我们的控件可以wrap_content
,那么我们就必须重写onMeasure( )
来指定onMeasure( )
的大小。
由MeasureSpec
我们知道了测量模式,也知道了测量大小。
我们重写onMeasure( )
(为啥重写后面会说)可以得到
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
我们查看super.onMeasure
发现系统调用了setMeasuredDimension
方法将测量后的宽高值设置好。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
{
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent))
{
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
有上面分析可知,如果我们要对宽高重新定义,那么我们要重写onMeasure
如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension(measuredWidth(widthMeasureSpec),measuredHeight(widthMeasureSpec));
}
//measureWidth 和measureHeight 是我们自己定义的方法
//参数则是对应的MeasureSpec对象,包含测量模式和测量值
public int measuredWidth(int measureSpec){
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if(specMode == MeasureSpec.EXACYLY){
result = specSize ;
}else{
result = 200;//这里的200是自己设置的默认值,单位为px
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);//如果我们的模式是AAT_MOST就取测量值和默认值中的较小一个。
}
}
return result;
}
//measureHeight的方式和measureWidth的差不多,这里就不写出来了,一会又完整代码
*** ----------------分割线,下面是完整代码加解释----------------***
自定义的MyView.class
package com.unibox.dm_setxfermode;
import android.content.Context;
import android.content.pm.PackageItemInfo;
import android.content.res.Resources;import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;
/** * Created by Administrator on 2016/2/11 0011. */
public class MyView extends View {
public MyView(Context context) {
this(context,null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
}
public int measureWidth(int measureSpec){
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = 200;
if (specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
public int measureHeight(int measureSpec){
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else {
result =200;
if (specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
}
对自定义view的使用activity_main.xml
下面是效果截图:
当都是精确值的时候,我将宽度设置为400px,宽度还是默认的200px(这里为了对比,所以用px)。精确值的情况下,宽高值都是跟随我们设置的值变化的。
当我们把宽高设置为match_parent
的时候:
当我们把宽高设置为wrap_content
.*** 其实我们这里已经可以猜到宽高就是我们result的默认值****:
到这里简单的把view的测量总结了一下,个人觉新手没必要在源码里面转,转也转不明白。只要知道:
- 控件绘制前都是经过系统测量的
- 测量的结果和模式都在MeasureSpec当中
- 系统默认是精确测量模式,要修改就要重写onMeasure方法,不然你设置属性为wrap_content是不起作用的.