基础自定义view步骤

本文介绍如何创建基础自定义View,包括在attrs.xml中定义属性,布局文件中添加命名空间,以及在构造方法中处理属性。通过解析attrs.xml,自定义View类能读取并应用设置的属性。在Android Studio中,添加命名空间如 xmlns:myview="http://schemas.android.com/apk/res-auto",而在Eclipse中则需注意命名空间的正确使用。

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

  想来说说基础版自定义View的步骤:

   实现自定义View的属性设置,需要:

  在values目录下建立attrs.xml文件,添加属性内容

  在布局文件中添加新的命名空间xmlns,然后可以使用命名空间给自定义的空间设置属性

  设置完属性之后,当然还要对其进行处理。在自定义View类中的构造方法中进行处理

    首先贴一段attrs的代码:

<resources>
   
    <attr name="myTextWord" format="string" />
    <attr name="myTextColor" format="color" />
    <attr name="myTextSize" format="dimension" />
    
    <declare-styleable name="MyView">
        <attr name="myTextWord" />
        <attr name="myTextColor" />
        <attr name="myTextSize" />
        
    </declare-styleable>
    
</resources>

   接着是命名空间的事,这个我被eclipse坑了好久,要是studio就没有这个问题了,所以最后说,最后再写。所以我们来看一看自定

义View的代码,我就不多介绍了,因为我的注释已经做的真的很详细了,再不懂真没办法了。多的不说,贴代码:

public class MyView extends View
{	//TextView的文字设定
	private String textWord;
	//TextView的颜色设定
	private int textColor;
	//TextView的字体大小设定
	private int textSize;
	/**
	 * Paint即画笔,画笔主要保存了颜色,样式等绘制信息  
	 * 画笔对象有很多设置方法,大体上可以分为两类。    
	 * 一类与图形绘制相关
	 * 一类与文本绘制相关
	 */
	private Paint mPaint;
	/**
	 * Rect类主要用于表示坐标系中的一块矩形区域,并可以对其做一些简单操作
	 * 这块矩形区域,需要用左上右下两个坐标点表示(left,top,right,bottom)
	 * 你也可以获取一个Rect实例的Width和Height
	 * 需要注意的一点是:
	 * 例如:Rect r=new Rect(100,50,300,500);
	 * 其实(300,500)这个点是不在矩阵区域内的,但是如果自己调用android自己的函数就没问题,因为android内部计算方式是统一的
	 */
	private Rect mBound;

	/**
	 * 自定义view时肯定要覆写构造方法的,目前构造参数在5.0已经达到了四个,平时我们一般使用的其实两个就够了。
	 * 第一个参数属于程序内实例化采用
	 * 在java代码创建视图时调用,如果由xml填充的视图就不会使用这个构造
	 * 第二个参数属于layout文件实例化,会将xml中的参数通过attributeSet传入到view中
	 * 在xml创建但没有指定style的时候调用
	 * 第三个参数属于style信息,也会从xml中带入
	 * 在xml创建,并指定了style的时候调用
	 * 这里的调用的虽然是第三个构造,但是defStyle是系统默认的数值,并没有由外部传入任何数据
	 */
	public MyView(Context context, AttributeSet attrs) {
		this(context,attrs,0);
	}

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

	public MyView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		/**
		 * TypedArray是一个数组,用来记录attributeSet的值,也是android提供给我们的一个工具类,
		 * 所以这个类是可以用,也可以不用的,不用的话可以用一个数组存放attributeSet中的参数属性值,不过写起来很麻烦
		 * TypedArray存放恢复obtainStyledAttributes(AttributeSet, int[], int, int)
		 * 或 obtainAttributes(AttributeSet, int[])值的一个数组容器,
		 * 当操作完成以后,一定要调用recycle()方法。
		 * 用于检索的索引值在这个结构对应的位置给obtainStyledAttributes属性。
		 * 使用这个类的时候,先要在valuse文件夹下创建:atts.xml文件
		 */
		TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyView, defStyle, 0);
		//返回我们所使用属性的个数
		int n = a.getIndexCount();

		for (int i = 0; i < n; i++)
		{	//返回属性id,自定义属性初始化和设置默认值
			int attr = a.getIndex(i);
			switch (attr)
			{
			case R.styleable.MyView_myTextWord:
				textWord = a.getString(attr);
				break;
			case R.styleable.MyView_myTextColor:
				// 默认颜色设置为黑色
				textColor = a.getColor(attr, Color.BLACK);
				break;
			case R.styleable.MyView_myTextSize:
				// 默认设置为16sp,TypeValue也可以把sp转化为px
				textSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
						TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
				break;

			}

		}
		a.recycle();

		/**
		 * 获得绘制文本的宽和高
		 */
		mPaint = new Paint();
		//绘本字体大小
		mPaint.setTextSize(textSize);
		// mPaint.setColor(mTitleTextColor);绘本字体颜色
		//view 的绘图范围
		mBound = new Rect();

		/**
		 * 这里的代码需要倒着看,倒着写就好理解一些
		 * 首先我们需要得到文本Text的视图大小(不是TextView视图大小)
		 * 需要传入的参数和实例有画笔mPaint,有mBound矩阵实例,有TextWord文字
		 * 并且需要规定好文本字体的大小,应为他会影响视图范围,所以上面代码中
		 * 设画笔颜色的代码可以注销,但设置文本字体的代码不能少。
		 */
		mPaint.getTextBounds(textWord, 0, textWord.length(), mBound);

		//这里是添加点击事件,动态的更改文本内容,程序员都知道,不用看,主要需呀看得就一句postInvalidate();
		this.setOnClickListener(new OnClickListener()
		{

			@Override
			public void onClick(View v)
			{	//设置文本呢
				textWord = setMyTextWord();
				/**
				 * android提供的更新ui界面的方法有两个
				 * 一个是Invalidate();
				 * 一个是postInvalidate();
				 * 其中前者是在UI线程自身中使用,而后者在非UI线程中使用。 
				 * 很显然两个用法是不同的,我们分开说:
				 * Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,
				 * 因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。 
				 * 利用invalidate()刷新界面实例化一个Handler对象,
				 * 并重写handleMessage方法调用invalidate()实现界面刷新
				 * 而在线程中通过sendMessage发送界面更新消息。
				 * 
				 * 使用postInvalidate()刷新界面使用postInvalidate则比较简单,
				 * 不需要handler,直接在线程中调用postInvalidate即可。
				 * 这里我们就直接调用了postInvalidate();这个方法,实现text文字的刷新了。
				 * 
				 * Android中View的绘制过程当Activity获得焦点时,它将被要求绘制自己的布局,
				 * Android framework将会处理绘制过程,Activity只需提供它的布局的根节点。
				 * 绘制过程从布局的根节点开始,从根节点开始测量和绘制整个layout tree。
				 * 每一个ViewGroup 负责要求它的每一个孩子被绘制,每一个View负责绘制自己。
				 * 因为整个树是按顺序遍历的,所以父节点会先被绘制,而兄弟节点会按照它们在树中出现的顺序被绘制。
				 */
				postInvalidate();
			}

		});
	}
	//略过,可以不看你
	public String setMyTextWord(){
		Set<Integer>set=new HashSet<Integer>();
		Random r=new Random();
		while(set.size()<4){
			int num=r.nextInt(10);
			set.add(num);
		}
		StringBuffer sb=new StringBuffer();
		for(Integer i:set){
			sb.append(i);
		}

		return sb.toString();
	}

	/**
	 * onMeasure方法,获取视图view的宽高尺寸
	 * widthMeasureSpec宽的详细测量值
	 * heightMeasureSpec高的详细测量值
	 * 表示的是view视图想要的大小宽高,但不一定就是最终实际的大小宽高
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
	{
		/**
		 * MeasureSpec这是一个封装在View里面的内部类
		 * 其内部处理的是measurespec详细测量中,size大小,model模式之间呢的关系
		 * MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求 
		 * MeasureSpec由size和mode组成。 
		 * 三种Mode: 
		 * 1.UNSPECIFIED 
		 * 父不没有对子施加任何约束,子可以是任意大小(也就是未指定) 
		 * (UNSPECIFIED在源码中的处理和EXACTLY一样。当View的宽高值设置为0的时候或者没有设置宽高时,模式为UNSPECIFIED 
		 * 2.EXACTLY 
		 * 父决定子的确切大小,子被限定在给定的边界里,忽略本身想要的大小。 
		 * (当设置width或height为match_parent时,模式为EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的) 
		 * 3.AT_MOST 
		 * 子最大可以达到的指定大小 
		 * (当设置为wrap_content时,模式为AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸) 
		 *  
		 * MeasureSpecs使用了二进制去减少对象的分配。 
		 */
		int width = 0;
		int height = 0;

		/**
		 * 设置宽度
		 */
		int specMode = MeasureSpec.getMode(widthMeasureSpec);
		int specSize = MeasureSpec.getSize(widthMeasureSpec);
		switch (specMode)
		{
		case MeasureSpec.EXACTLY:// 明确指定了
			width = getPaddingLeft() + getPaddingRight() + specSize;
			break;
		case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
			width = getPaddingLeft() + getPaddingRight() + mBound.width();
			break;
		}

		/**
		 * 设置高度
		 */
		specMode = MeasureSpec.getMode(heightMeasureSpec);
		specSize = MeasureSpec.getSize(heightMeasureSpec);
		switch (specMode)
		{
		case MeasureSpec.EXACTLY:// 明确指定了
			height = getPaddingTop() + getPaddingBottom() + specSize;
			break;
		case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
			height = getPaddingTop() + getPaddingBottom() + mBound.height();
			break;
		}

		/**
		 * 在onMeasure中只调用了setMeasuredDimension()方法,接受两个参数,
		 * 这两个参数是通过getDefaultSize方法得到的
		 * getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
		 * 这里就是获取最小宽度作为默认值,然后再根据具体的测量值和选用的模式来得到widthMeasureSpec。
		 * heightMeasureSpec同理。
		 * 之后将widthMeasureSpec,heightMeasureSpec传入setMeasuredDimension()方法。
		 */
		setMeasuredDimension(width, height);

	}


	/**
	 * 在这里就对canvas解释一下
	 * Canvas类(android.graphics.Canvas)。
	 * Canvas类就是表示一块画布,你可以在上面画你想画的东西。
	 * 当然,你还可以设置画布的属性,如画布的颜色/尺寸等。
	 * canvas提供的方法有很多,这里就不细说了,自己百度,但是就我被坑惨了的一点来谈谈。
	 * canvas.drawText方法,这个方法中第二,第三两个参数
	 * 第二的参数标示的是视图左边的距离
	 * 第三个参数标示的是基准线baseline的设置
	 * 它的baseline 指的是这个UI控件中文字Text的baseline 到UI控件顶端的偏移值
	 * 可以理解为text下面那条看不见的线
	 * 如果baseline的设置和左距的设置一样,很有可能你找不到你的text,只能看见背景图
	 * 两种设置方式可供参考吧,一种如下
	 * 另一种很取巧,不过试过,能用,getHeight()/2+mBound.Height()/2+6;
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		//画笔填充背景颜色
		mPaint.setColor(Color.GRAY);
		//画布画背景区域
		canvas.drawRect(0, 0,getMeasuredWidth(),getMeasuredHeight(),mPaint);
		//画笔填充文字颜色
		mPaint.setColor(textColor);
		//画布画文字
		canvas.drawText(textWord, getWidth()/2-mBound.width()/2, (getHeight()-mBound.height())/2+mBound.height()-6, mPaint);
	}
}

    最后来谈一谈命名空间,首先让我们来看看命名空间是什么鬼,有什么作用。

    xmlns:android="http://schemas.android.com/apk/res/android"

声明xml命名空间。xmlns意思为“xml namespace”.冒号后面是给这个引用起的别名。
    schemas是xml文档的两种约束文件其中的一种,规定了xml中有哪些元素(标签)、元素有哪些属性及各元素的关系,当然从面向    对象的角度理解schemas文

件可以认为它是被约束的xml文档的“类”或称为“模板”。

早期或简单的xml用的是另一种约束,称为DTD,这东西大家天天都见到。html/xhtml中都存在(早期的html可能没有),如"<!DOCTYPE html PUBLIC 

"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"。

现在大部分xml文档的约束都换成schema了,原因是schema本身也是xml,二schema扩展性强。

首先说说在studio中添加命名空间,就是添加xmlns:myview="http://schemas.android.com/apk/res-auto"就ok了,myview  就是你的可用命名空间了。

但是在eclipse中,你使用xmlns:myview="http://schemas.android.com/apk/res/包名"你这么写是会报警的,警告是unused namespace,并且你在自定义view中调用自己定义的属性,使用自己命名空间myview  是调不出任何东西的,这个时候不用慌,要相信自己,自己的命名空间,自己的属性全部用敲的,不要用提示,也提示不出鬼东西,写完保存,然后你会发现属性能用,这就ok了。

都是基础知识,大家不要嫌弃。

注释一句,代码是鸿洋老师的代码,想看原版的,给个链接:

http://blog.csdn.net/lmj623565791/article/details/24252901/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值