自定义View_onDraw,onMeasure方法

本文深入解析自定义View的实现原理,重点介绍onMeasure方法的作用及其内部实现细节,并探讨MeasureSpec的各种模式及其应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

package xena.view;

import xena.act.R;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class MyView extends View {
	private Context context;
	private int r = 10;
	private String str = "小";
	private final String NS = "http://www.hqyj.com";// 名称空间
	private boolean flag;

	public float getR() {
		return r;
	}

	public void setR(int r) {
		this.r = r;
		this.invalidate();//触发onDraw方法的执行
	}

	public String getStr() {
		return str;
	}

	public void setStr(String str) {
		this.str = str;
		this.invalidate();//触发onDraw方法的执行
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
		this.invalidate();//触发onDraw方法的执行
	}

	// 用于自定义View当作标签时用的
	public MyView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.context = context;
		// 参数1:名称空间, 参数2:自定义属性名, 取失败时当作默认值返回
		this.r = attrs.getAttributeIntValue(NS, "r", 20);
	//	this.flag = attrs.getAttributeBooleanValue(NS, "flag", false);
		// 参数1:我称空间, 参数2: 自定义属性名
		// this.str = attrs.getAttributeValue(NS, "text");
		// 如果属性值是资源文件id
		// int getAttributeResourceValue(String namespace, String attributeName,
		// int defaultValue);
		
		int id = attrs.getAttributeResourceValue(NS, "text", -1);
		if (id != -1) {
			// int id = R.string.hello_world;
			Resources resources = this.context.getResources();
			this.str = resources.getString(id);
		}else {
			this.str = attrs.getAttributeValue(NS, "text");
		}
	}

	// 用于new MyView(...)用的
	public MyView(Context context) {
		super(context);
	}
	private int getwidth(int widthMeasureSpec) {
		int width = 10;
		int width_mode = MeasureSpec.getMode(widthMeasureSpec);
		if(width_mode==MeasureSpec.EXACTLY) {
			width = MeasureSpec.getSize(widthMeasureSpec);
		}else if(width_mode==MeasureSpec.AT_MOST) {
			width = (int) (2*r);
		}else if(width_mode==MeasureSpec.UNSPECIFIED) {
			width = 100;
		}
		return width;
	}
	// 00  11  10  11
	
	private int getheight(int heightMeasureSpec) {
		int hight = 10;
		int hight_mode = MeasureSpec.getMode(heightMeasureSpec);
		if(hight_mode==MeasureSpec.EXACTLY) {
			hight = MeasureSpec.getSize(heightMeasureSpec);
		}else if(hight_mode==MeasureSpec.AT_MOST) {
			hight= (int) (2*r);
		}else if(hight_mode==MeasureSpec.UNSPECIFIED) {
			hight = 100;
		}
		return hight;
	}
	//在onDraw前调用
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int width=0, hight=0;
		width = this.getwidth(widthMeasureSpec);
		hight = this.getheight(heightMeasureSpec);
		this.setMeasuredDimension(width, hight);
	}
//	// 用于绘制界面上的内容,当界面显示时调用
	@Override
	protected void onDraw(Canvas canvas) {// canvas画布对象
		super.onDraw(canvas);
		// 创建笔 画
		Paint paint = new Paint();
		paint.setColor(Color.BLUE);
		// 画圆
		canvas.drawCircle(r, r, r, paint);
		paint.setColor(Color.RED);

		if (flag) {
			canvas.drawCircle(r, r, 5, paint);
		}
		// 画字
		// 参数1:被绘制的字符串, 参数2,3:指字符串每一个字符的左下角坐标
		paint.setTextSize(30);// 设置文字大小
		canvas.drawText(this.str, r, r, paint);
	}
}

(1)一般情况重写onMeasure()方法作用是为了自定义View尺寸的规则,如果你的自定义View的尺寸是根据父控件行为一致,就不需要重写onMeasure()方法 

(2)如果不重写onMeasure方法,那么自定义view的尺寸默认就和父控件一样大小,当然也可以在布局文件里面写死宽高,而重写该方法可以根据自己的需求设置自定义view大小
(3)widthMeasureSpec和heightMeasureSpec这两个值通常情况下都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。

1.

wrap--AT_MOST

fill--EXACTLY

固定值--EXACTLY

上边是大家都知道的,但是一直很疑惑UNSPECIFIED什么情况发生,今天测了下,发现在使用weigt时会调用,因为此时定义的值为0dp,这不就是UNSPECIFIED嘛!

父       match_parent     match_parent            match_parent            wrap_content      wrap_content        wrap_content       200px                  200px                    200px    
子       match_parent     100px                        wrap_content             match_parent     100px                    wrap_content       100px                  match_parent        wrap_content
结果    exactly(fill)          exactly(100px)          at_most(fill)            at_most    (fill)     exactly(100xp)      at_most(fill)           exactly(100px)    exactly(200px)       at_most(200xp)

1.有at_most  适合自定义一个大小,(子的大小超不过父大小)
2.有exactly 则适合测出的大小. 
3,有unspecified, 适合任意指定大小,(子的大小可超过父大小)
 可以说重载onMeasure(),onLayout(),onDraw()三个函数构建了自定义View的外观形象。再加上onTouchEvent()等重载视图的行为,可以构建任何我们需要的可感知到的自定义View。

不管是自定义View还是系统提供的TextView这些,它们都必须放置在LinearLayout等一些ViewGroup中,因此理论上我们可以很好的理解onMeasure(),onLayout(),onDraw()这三个函数:1.View本身大小多少,这由onMeasure()决定;2.View在ViewGroup中的位置如何,这由onLayout()决定;3.绘制View,onDraw()定义了如何绘制这个View。

widthMeasureSpec, heightMeasureSpec这两个参数是从哪里来的?onMeasure()函数由包含这个View的具体的ViewGroup调用,因此值也是从这个ViewGroup中传入的。这里我直接给出答案:子类View的这两个参数,由ViewGroup中的layout_width,layout_height和padding以及View自身的layout_margin共同决定。权值weight也是尤其需要考虑的因素,有它的存在情况可能会稍微复杂点。

这个值由高32位组成,最高的两位保存的值叫specMode,可以通过如代码中所示的MeasureSpec.getMode()获取;低30位为specSize,同样可以由MeasureSpec.getSize()获取

所有的View的onMeasure()的最后一行都会调用setMeasureDimension()函数的作用——这个函数调用中传进去的值是View最终的视图大小。也就是说onMeasure()中之前所作的所有工作都是为了最后这一句话服务的。

specMode一共有三种可能:

MeasureSpec.EXACTLY:父视图希望子视图的大小应该是specSize中指定的。

MeasureSpec.AT_MOST:子视图的大小最多是specSize中指定的值,也就是说不建议子视图的大小超过specSize中给定的值。

MeasureSpec.UNSPECIFIED:我们可以随意指定视图的大小。


MATCH_PARENT对应于EXACTLY,WRAP_CONTENT对应于AT_MOST,其他情况也对应于EXACTLY,它和MATCH_PARENT的区别在于size值不一样。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值