import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import androidx.annotation.Nullable;
/**
* @Author: david.lvfujiang
* @Date: 2020/1/16
* @Describe:
*/
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
//画个圆
canvas.drawCircle(200,200,200,paint);
}
}
<com.example.MyView
android:id="@+id/myView"
android:background="@color/colorPrimaryDark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"></com.example.MyView>
我们自定义一个
view
,默认测量,在onDraw()
画一个圆,在xml
中给view
的宽度、高度都是wrap_content
,发现view
会沾满整个父控件
如果我们想自己设置
view
的高度和宽度则需要重写onMeasure()
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import androidx.annotation.Nullable;
/**
* @Author: david.lvfujiang
* @Date: 2020/1/16
* @Describe:
*/
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//高度自己计算,根据圆的半径知道圆的高度是400像素
int height = 400;
//解析父类传入的限制
int width = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//尺寸修正
resolveSize(400,widthMeasureSpec);
resolveSize(width,heightMeasureSpec);
//保存尺寸
setMeasuredDimension(width,height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
canvas.drawCircle(200,200,200,paint);
}
}
onMeasure()
方法传入的俩个参数是父控件对子view
的宽度和高度限制,它们由父控件计算得到。使用MeasureSpec
类可以对它们进行解析得到尺寸和限制的规则。限制规则的分类:
UNSPECIFIED:
不限制
AT_MOST:
任意大小但不能超过父控件
EXACTLY:
固定值
如果限制规则是AT_MOST
,则解析得到的尺寸是父类最大的高度或者宽度。如果是UNSPECIFIED
或EXACTLY
解析得到的尺寸就是在xml
内指定的尺寸。
resolveSize()
是尺寸修正的方法,例如view
的限制是AT_MOST
,它不能超过父控件的范围,但是我们自己计算出的高度已经超过父控件的范围,这时候我们就需要修正尺寸,不然会发现异常。
public static int resolveSize(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:
//size小于父类范围则直接返回,大于则返回specSize,specSize是父类最大的范围尺寸
result = Math.min(size, specSize);
break;
case MeasureSpec.EXACTLY:
//限制是EXACTLY,精确值。则直接返回
result = specSize;
break;
}
return result;
}
整体的测量流程大致就是:自己测量view
需要的高度和宽度,调用resolveSize()
方法修正尺寸,确保符合父类的限制。最后调用 setMeasuredDimension(width,height);
方法保存。
上诉例子是根据圆来计算view的高度,宽度则引用父类传进来的尺寸,因此执行得到: