自定义View的基本知识和步骤

当初刚入门Android时用的都是原生的控件,刚开始觉得原生的控件其实也可以满足当时的一些学校的小项目开发,也就没怎么深入自定义view。但参加工作后,发现有时美工给的设计图某些功能实现起来还是挺刁钻的,于是便开始了自定义view的学习。或许很多人都觉得自定义view是个很难的东西,其实当你真正用心去弄了几个自定义view之后就会发现其实也并没有那么难。由于个人工作效率还是蛮快的,项目之余闲蛋疼的很,常常自己看到那些好玩的东西就用自定义view画下来。

自定义view的基本步骤无非也就那么几步:

1. values文件夹下创建attrs.xml文件,在attrs里添加你想给自己view添加的属性。例:

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyTextView">
        <attr name="text" format="string"/>
        <attr name="textSize" format="dimension"/>
    </declare-styleable>
</resources>

declare-styleable是你自定义view的一套新定义的属性,下面包含了你要定义的各种属性attr

format是指定attr属性的单位,其中包括:

(1) reference: 引用某一资源,如:src="@drawable/sourcename";

(2)color:颜色,如color="#ff0000";

(3)boolean:布尔值,true或false;

(4)dimension:尺寸值,如sp,dp,px;

(5)float:浮点型,也就是小数,如0.5, 1.8;

(6)integer:整形, 如 1, 100;

(7)string:字符串

(8)fraction:百分数, 如100%

(9)enum:枚举,如 orientation="vertical"

(10)flag:位或运算,如gravity="centerHorizontal | right"

format的详细格式可以参考这

2. 创建类文件,添加构造体,获取属性并初始化变量。例:

public class MyTextView extends View {

	private String text;
	private int textSize;
	private Paint paint;

	public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		// TODO Auto-generated constructor stub

		//顾名思义,获取风格和属性,得到一个包含各种属性的数组array,包括你自定义的attr属性
		//R.styleable.MyTextView就是一个指向你刚在attrs.xml中自定义的属性数组的id
		TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.MyTextView);

		//获取文本内容
		text=array.getString(R.styleable.MyTextView_text);

		//获取文本字体大小,第二个参数是默认值,就是没有使用你定义属性时的提供值, sp2px()是sp转px函数。
		textSize=array.getDimensionPixelSize(R.styleable.MyTextView_textSize, sp2px(18));

		//这玩意初始化完成后务必回收
		array.recycle();

		//画笔初始化,用于后面的绘图;
		paint=new Paint();

		//至此,完成变量的初始化
	}

	public MyTextView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
		// TODO Auto-generated constructor stub

		//若使用xml加载view,必须要重写上面或这个构造体
	}

	public MyTextView(Context context) {
		this(context, null);
		// TODO Auto-generated constructor stub

		//统一到第一个构造体进行初始化
	}
}

3. 重写onMeasure(),,测量view,确定view的尺寸。(这步并不是自定义view的必要步骤,但重写后可以适应wrap_content这参数等)

这一步因为不是必须,可以跳过,但当你在设置layout_width和layout_height的时候只能设match_parent或指定值,不然设置wrap_content会很别扭。

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub

		//widthMeasureSpec参数可以被MeasureSpec类的静态方法解析出宽度计算的模式和值
		//模式有AT_MOST, EXACTLY, UNSPECIFIED
		int width=measureViewWidth(widthMeasureSpec);

		//计算高度,和宽度处理差不多
		int height=measureViewHeight(heightMeasureSpec);

		setMeasuredDimension(width, height);
	}

	//处理view的宽度
	private int measureViewWidth(int widthSpec){
		int result=0;

		int mode=MeasureSpec.getMode(widthSpec);
		int width=MeasureSpec.getSize(widthSpec);

		//对应wrap_content, viewgroup只提供一个最大值,子view尺寸不能超过这个值
		//这种情况下,可以根据内容大小设置view的大小,如令view的width=text的宽度
		if(mode==MeasureSpec.AT_MOST){
			int textWidth=measureTextWidth();
			result=Math.min(textWidth, width);
		}
		//对应match_parent或指定的值,viewgourp提供的值为parent的宽度或指定的宽度
		if(mode==MeasureSpec.EXACTLY){
			result=width;
		}

		return result;
	}

	//处理view的高度
	private int measureViewHeight(int heightSpec){
		int result=0;

		int mode=MeasureSpec.getMode(heightSpec);
		int height=MeasureSpec.getSize(heightSpec);

		if(mode==MeasureSpec.AT_MOST){
			int textHeight=measureTextHeight();
			result=Math.min(textHeight, height);
		}
		if(mode==MeasureSpec.EXACTLY){
			result=height;
		}

		return result;
	}

	//测量text的宽度
	private int measureTextWidth(){
		int textWidth=(int) paint.measureText(text);
		return textWidth;
	}

	//测量text的高度
	private int measureTextHeight(){
		FontMetrics fm=paint.getFontMetrics();
		int textHeight=(int) (fm.bottom-fm.top);
		return textHeight;
	}

widthMeasureSpec和heightMeasureSpec两个值是viewgroup传给子view的,通过MeasureSpec的解析后再根据模式来计算最后的值,若是wrap_content则计算view内容的尺寸再计算view的尺寸,若是match_parent或指定值,则直接使用viewgroup传过来的值,经过处理后,最后还要调用setMeasuredDimension来确定view的最终尺寸。

             

view的尺寸设置为wrap_content情况下,左边没有重写onMeasure,viewgroup会传一个父组件可分配给子view的最大尺寸,所以子view的尺寸便和父容器一样大了;而右边的重写了onMeasure之后,因为经过处理,使子view尺寸等于文本内容大小,所以尺寸只有文本大小。

4.重写onDraw(), 在一块空白的View上绘制你想要的东西,这一步是最重要的。如:

	@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub

		//把view的背景绘成黄色
		canvas.drawColor(Color.YELLOW);

		//测量绘制字体的高度
		FontMetrics fm=paint.getFontMetrics();
		int textHeight=(int) (fm.bottom-fm.top);

		//参数1.要绘制的文本, 2.文本左边位于view的x坐标, 3.文本baseline位于view的y坐标, 4.画笔
		//因为baseline到文本底部的距离无法获取,只能取文本高度的3/10
		canvas.drawText(text, 0, textHeight-textHeight*0.3f, paint);
	}

canvas类封装了一大堆绘图工具,所以画图并不是很难的事,不过若要实现比较复杂的图,那就需要懂得一些几何计算知识了,此处只是把文本简单地画上去而已。

还有那个文本的高度处理不懂的可以百度搜索android baseline或android测量字体高度。

5. 在布局文件里使用,记得添加属性使用的空间,也就是最顶部xmlns=xxxxxx,那一串东西。

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res/com.example.test"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

   	<com.example.test.MyTextView
   	    android:layout_width="wrap_content"
   	    android:layout_height="wrap_content"
   	    custom:text="aaaaaaaaaaagggggggggggggggg"
   	    custom:textSize="18sp"/>

</LinearLayout>

下面的custom属性命名空间必须加上xmlns:custom="http://schemas.android.com/apk/res/com.example.test"才能用

格式:  xmlns:定义的空间名称="http://schemas.android.com/apk/res/在AndroidManifest中的包名。



至此,一个简单的自定义view就实现了,看起来代码挺多的,但真正去把它写完后,就感觉其实自定义view也就这样而已。当然,简单的view只要实现以上几个步骤,基本就可以满足需要了,如果要实现华丽的效果,仅仅是上面几个步骤不够的, 还要重新onTouchEvent等函数,使view能处理触摸事件从而达到交互效果。

下面贴出完整代码:

MyTextView.java

public class MyTextView extends View {

	private String text;
	private int textSize;
	private Paint paint;

	public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		// TODO Auto-generated constructor stub

		//顾名思义,获取风格和属性,得到一个包含各种属性的数组array,包括你自定义的attr属性
		//R.styleable.MyTextView就是一个指向你刚在attrs.xml中自定义的属性数组的id
		TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.MyTextView);

		//获取文本内容
		text=array.getString(R.styleable.MyTextView_text);

		//获取文本字体大小,第二个参数是默认值,就是没有使用你定义属性时的提供值, sp2px()是sp转px函数。
		textSize=array.getDimensionPixelSize(R.styleable.MyTextView_textSize, sp2px(18));

		//这玩意初始化完成后务必回收
		array.recycle();

		//画笔初始化,用于后面的绘图;
		paint=new Paint();

		//至此,完成变量的初始化
		paint.setTextSize(textSize);
	}

	public MyTextView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
		// TODO Auto-generated constructor stub

		//若使用xml加载view,必须要重写上面或这个构造体
	}

	public MyTextView(Context context) {
		this(context, null);
		// TODO Auto-generated constructor stub

		//统一到第一个构造体进行初始化
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub

		//widthMeasureSpec参数可以被MeasureSpec类的静态方法解析出宽度计算的模式和值
		//模式有AT_MOST, EXACTLY, UNSPECIFIED
		int width=measureViewWidth(widthMeasureSpec);

		//计算高度,和宽度处理差不多
		int height=measureViewHeight(heightMeasureSpec);

		setMeasuredDimension(width, height);
	}

	//处理view的宽度
	private int measureViewWidth(int widthSpec){
		int result=0;

		int mode=MeasureSpec.getMode(widthSpec);
		int width=MeasureSpec.getSize(widthSpec);

		//对应wrap_content, viewgroup只提供一个最大值,子view尺寸不能超过这个值
		//这种情况下,可以根据内容大小设置view的大小,如令view的width=text的宽度
		if(mode==MeasureSpec.AT_MOST){
			int textWidth=measureTextWidth();
			result=Math.min(textWidth, width);
		}
		//对应match_parent或指定的值,viewgourp提供的值为parent的宽度或指定的宽度
		if(mode==MeasureSpec.EXACTLY){
			result=width;
		}

		return result;
	}

	//处理view的高度
	private int measureViewHeight(int heightSpec){
		int result=0;

		int mode=MeasureSpec.getMode(heightSpec);
		int height=MeasureSpec.getSize(heightSpec);

		if(mode==MeasureSpec.AT_MOST){
			int textHeight=measureTextHeight();
			result=Math.min(textHeight, height);
		}
		if(mode==MeasureSpec.EXACTLY){
			result=height;
		}

		return result;
	}

	//测量text的宽度
	private int measureTextWidth(){
		int textWidth=(int) paint.measureText(text);
		return textWidth;
	}

	//测量text的高度
	private int measureTextHeight(){
		FontMetrics fm=paint.getFontMetrics();
		int textHeight=(int) (fm.bottom-fm.top);
		return textHeight;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub

		//把view的背景绘成黄色
		canvas.drawColor(Color.YELLOW);

		//测量绘制字体的高度
		FontMetrics fm=paint.getFontMetrics();
		int textHeight=(int) (fm.bottom-fm.top);

		//参数1.要绘制的文本, 2.文本左边位于view的x坐标, 3.文本baseline位于view的y坐标, 4.画笔
		//因为baseline到文本底部的距离无法获取,只能取文本高度的3/10
		canvas.drawText(text, 0, textHeight-textHeight*0.3f, paint);
	}

	//sp转px单位
	private int sp2px(int sp){
		return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
	}

}

布局文件很简单就不贴了, attrs文件也很简单,在上面了。

时间: 2024-10-12 21:48:03

自定义View的基本知识和步骤的相关文章

Android官方开发文档Training系列课程中文版:创建自定义View之View的绘制

原文地址:http://android.xsoftlab.net/training/custom-views/custom-drawing.html#draw 自定义View最重要的部分就是它的样子了.自定义View的绘制根据应用的需要或者简单亦或者复杂.这节课的内容涵盖了大多数通用的知识点. 重写onDraw()方法 绘制自定义View很重要的一个步骤就是重写它的onDraw()方法.该方法含有一个Canvas对象作为参数,用来使View绘制它本身的内容.Canvas类定义了用于绘制文本,线条

Android查缺补漏(View篇)--自定义 View 的基本流程

View是Android很重要的一部分,常用的View有Button.TextView.EditView.ListView.GridView.各种layout等等,开发者通过对这些View的各种组合以形成丰富多彩的交互界面,一个应用中界面交互的体验往往在应用的受欢迎程度上起了很关键得作用,所以开发者们大多会想方设法的做出一个更加精美的界面,例如:通过自定义View.深入学习View的原理以便更好的对其优化使其在操作起来更加流畅等等,也正因为如此,在面试中View也常常作为面试官重点考察的对象之一

自定义View(一)

一.了解ViewRoot和DecorView 1.ViewRoot 从源码可以看出ViewRoot是ViewParent的实现类 public final class ViewRoot extends Handler implements ViewParent, ViewRoot对应于的ViewRootImp也是ViewParent的实现类 public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callback

Android自定义View(四)----一步一步教你实现QQ健康界面

最近一直在学习自定义View相关的知识,今天给大家带来的是QQ健康界面的实现.先看效果图: 可以设置数字颜色,字体颜色,运动步数,运动排名,运动平均步数,虚线下方的蓝色指示条的长度会随着平均步数改变而进行变化.整体效果还是和QQ运动健康界面很像的. 自定义View四部曲,一起来看看怎么实现的. 1.自定义view的属性: <?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定

Android自定义view(初级篇)

Q1:为什么要自定义view? A:由于很多系统自带的view满足不了当前设计需求或者为了达到更良好的用户体验,增加UI的美化效果,就需要自定view Q2:自定义view有那几个步骤? A:>用户可根据需要extends View这个父类,然后重写父类的方法:如:onDraw();onMeasure()等: >如果用户在自定义View事需要添加属性,则必须在values文件夹下新建"attrs.xml"文件,在其中添加自定义属性. 下面来进行自定义view的学习. 一.最

Android知识梳理之自定义View

虽然android本身给我们提供了形形色色的控件,基本能够满足日常开发的需求,但是面对日益同质化的app界面,和不同的业务需求.我们可能就需要自定义一些View来获得比较好的效果.自定义View是android开发者走向高级开发工程师必须要走的一关. 转载请标明出处:http://blog.csdn.net/unreliable_narrator/article/details/51274264 一,构造函数: 当我们创建一个类去继承View的时候,会要求我们至少去实现一个构造函数. publi

[原] Android 自定义View步骤

例子如下:Android 自定义View 密码框 例子 1 良好的自定义View 易用,标准,开放. 一个设计良好的自定义view和其他设计良好的类很像.封装了某个具有易用性接口的功能组合,这些功能能够有效地使用CPU和内存,并且十分开放的.但是,除了开始一个设计良好的类之外,一个自定义view应该: l 符合安卓标准 l 提供能够在Android XML布局中工作的自定义样式属性 l 发送可访问的事件 l 与多个Android平台兼容. Android框架提供了一套基本的类和XML标签来帮您创

android中自定义view涉及到的绘制知识

android中自定义view的过程中,需要了解的绘制知识. 1.画笔paint: 画笔设置: <span style="font-size:14px;"> paint.setAntiAlias(true);//抗锯齿功能 paint.setColor(Color.RED); //设置画笔颜色 paint.setStyle(Style.FILL);//设置填充样式 paint.setStrokeWidth(30);//设置画笔宽度 paint.setShadowLayer(

继承ViewGroup自定义View:步骤、attrs.xml、TypedArray

时间:2015年12月22日19:01:46 自定义View的实现步骤: 1.写一个自定义控件类,这个类就是你的自定义控件的实现. 2.在res/values目录下建立一个attrs.xml的文件,在这个文件中增加对控件的自定义属性的定义. 3.使用带AttributeSet参数的类的构造函数,并在构造函数中将自定义控件类中变量与attrs.xml中的属性连接起来. 4.在自定义控件类中使用这些已经连接的属性变量. 5.将自定义的控件类定义到布局用的xml文件中去. 6.在界面中生成此自定义控件