Android自定义主要有3种,自定义View、自定义ViewGroup、继承重写系统控件

本文主要讲解Android中如何自定义View

Android打造自定义控件,大体的思路主要有以下五点:

1.创建自定义属性,在res/values目录下创建attrs.xml文件,声明自定义控件的属性

2.创建自定义View类,继承于View类,重写View的三个构造方法

3.通过TypeArray获得各个自定义属性,并将Paint设置为这些属性的内容

4.重写onMeasure方法,设置好视图在界面上所显示的大小

5.重写onDraw方法,通过paint和canvas渲染出自定义控件




1.创建自定义属性,在res/values目录下创建attrs.xml文件,声明自定义控件的属性

<?xml version="1.0" encoding="utf-8"?><resources>        <declare-styleable name="myTextView">        <attr name="mytextContent" format="string" type="string"></attr><attr name="mytextColor" format="color" type="color"></attr><attr name="mytextSize" format="dimension" type="dimension"></attr>    </declare-styleable>      </resources>


声明自定义属性有两个作用,一方面可以让我们在布局文件中直接使用这些属性,另一方面,在xml中声明属性之后会在R类中生成对应的资源ID,方便到时候TypeArray的使用(关于TypeArray见下文)
每个attr标签表示声明一个属性,name是属性的名字,format是属性的格式,比如string表示这个属性必须为字符串,color表示这个属性必须为颜色类型,dimension表示这个属性必须为像素即dp、sp之类的



2.创建自定义View类,继承于View类,重写View的三个构造方法

public class MyTextView extends View implements View.OnClickListener{private String mytextContent;private int mytextColor;private int mytextSize;//画笔,用于绘制图形时使用private Paint paint;//矩形对象,用于计算文字位置时使用private Rect rect;public MyTextView(Context context) {// TODO Auto-generated constructor stubthis(context,null);}public MyTextView(Context context, AttributeSet attrs) {// TODO Auto-generated constructor stubthis(context,attrs,0);}public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// TODO Auto-generated constructor stub}}


可以看到三个构造方法的区别在于参数
public MyTextView(Context context) 【通过传入上下文对象来创建view】

public MyTextView(Context context, AttributeSet attrs) 【AttributeSet类型是用来获得我们声明的各个属性,当我们在xml布局文件中声明自定义View时,就会调用此构造方法】

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) 【多了一个defStyleAttr属性,当我们需要为view设定style时才会用到】

另外,我们对前两个构造方法都调用了this(),学过java的都知道,this表示调用本类的构造方法,在第一个构造方法中,我们调用了 this(context,null);其实是调用了第二个构造方法,在第二个构造方法中调用了this(context,attrs,0);其实是调用了第三个构造方法,所以这样写的好处是无论我们使用哪个构造方法,最终都会进到第三个构造方法,所以接下来我们要做的便是在第三个构造方法中获得我们的属性



3.通过TypeArray获得各个自定义属性,并将Paint设置为这些属性的内容

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// TODO Auto-generated constructor stubTypedArray array = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.myTextView, defStyleAttr, 0);//获得属性,并设置默认值mytextContent = array.getString(R.styleable.myTextView_mytextContent);mytextColor = array.getColor(R.styleable.myTextView_mytextColor,Color.WHITE);mytextSize = array.getDimensionPixelSize(R.styleable.myTextView_mytextSize, 30);array.recycle();paint = new Paint();//将画笔的文字大小设置为我们定义的大小paint.setTextSize(mytextSize);rect = new Rect();/*** 此方法可以获得文字所在的矩形区域,并赋给rect* 参数1:传入文字的内容* 参数2:传入文字起始的长度,一般为0* 参数3:传入文字结束的长度,一般为text.length* 参数4:传入一个Rect矩形对象*/paint.getTextBounds(mytextContent, 0, mytextContent.length(), rect);}


刚才在上文中已说过,此构造方法的AttributeSet参数可以得到我们在attr中声明的属性,那为什么此处还要通过TypeArray来获得呢?TypeArray有什么用?其实如果我们是直接通过AttributeSet获得属性的话,还需要解析才能获得我们需要的各个属性的值以及格式,而TypeArray帮我们把这些操作都封装好了,所以通过TypeArray可以很方便的获取到我们的属性



4.重写onMeasure方法,设置好视图在界面上所显示的大小

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stubsuper.onMeasure(widthMeasureSpec, heightMeasureSpec);//setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);}


对这个方法的使用见下文



5.重写onDraw方法,通过paint和canvas渲染出自定义控件

@Overrideprotected void onDraw(Canvas canvas) {// TODO Auto-generated method stubpaint.setColor(Color.BLACK);//canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);/*** 参数1:圆心的横坐标* 参数2:圆心的纵坐标* 参数3:圆的半径* 参数4:用来绘制的画笔*/canvas.drawCircle(getMeasuredWidth()/2, getMeasuredHeight()/2, getMeasuredWidth()/2, paint);paint.setColor(Color.RED);canvas.drawCircle(getMeasuredWidth()/2, getMeasuredHeight()/2, getMeasuredWidth()/3, paint);paint.setColor(mytextColor);canvas.drawText(mytextContent, getWidth() / 2 - rect.width() / 2, getHeight() / 2 + rect.height() / 2, paint);}


我们在这里通过canvas调用了两次drawCircle,即绘制了两个圆形,注意到第二个圆形的半径为getMeasuredWidth()/3,比第一个圆的半径小了,而圆心又与第一个圆一致,所以待会儿绘制出来的效果就是一个小圆叠在一个大圆前面,在每次绘制圆之前都调用了paint设置颜色,是为了两个圆形的颜色不一样。
最后再次设置paint颜色为mytextColor,即我们的文字内容的颜色,然后通过canvas.drawText进行文字的绘制,这里之所以getWidth() / 2 - rect.width() / 2是因为将View的宽度的一半减去文字内容的宽度的一半,得到的就是文字内容的左上角的横坐标(可以自行画图理解),纵坐标也是同理。



至此,我们完成了基本的定义,接下来就是在布局文件中使用它了:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    xmlns:mytext="http://schemas.android.com/apk/res/com.example.myview"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="${relativePackage}.${activityClass}" >    <com.example.view.MyTextView         android:layout_width="200dp"        android:layout_height="200dp"        mytext:mytextContent="1"        mytext:mytextColor="#FFF"        mytext:mytextSize="20sp"            /></RelativeLayout>


xmlns:mytext="http://schemas.android.com/apk/res/com.example.myview"是命名空间的声明,等下我们声明自定义属性的时候需要用到,这里的路径是http://scheam.....res/+项目包名


运行



运行之后,发现在屏幕成功出现了一个圆形背景中间带有文字内容,这里我们设置的宽和高都是200dp,如果将layout_width和layout_height都设置为wrap_content,会发现它并不像我们平时那样会自动有一个默认大小,而是像fill_parent一样的填充屏幕,很明显这不是我们愿意看到的,这个时候就需要用到onMeasure方法了:

首先为我们的自定义类加上两个私有成员标量:

//wrap_content时默认的宽度private final int DEFAULT_WIDTH = 200;//wrap_content时默认的高度private final int DEFAULT_HEIGHT = 200;


重写onMeasure方法:

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stub//super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width;  //最终的宽度int height;   //最终的高度int wspecMode = MeasureSpec.getMode(widthMeasureSpec);int wspecSize = MeasureSpec.getSize(widthMeasureSpec);int hspecMode = MeasureSpec.getMode(heightMeasureSpec);int hspecSize = MeasureSpec.getSize(heightMeasureSpec);if(wspecMode==MeasureSpec.EXACTLY){width = wspecSize;}else{width = DEFAULT_WIDTH;}if(hspecMode==MeasureSpec.EXACTLY){height = hspecSize;}else{height = DEFAULT_HEIGHT;}setMeasuredDimension(width, height);}


其中,有两个关键的方法,getModegetSize,getSize是获得用户所设定的view的宽高,getMode是获得这个属性的模式,Mode有三种
MeasureSpec.EXACTLY【设置了明确的值或者是MATCH_PARENT】

MeasureSpec.AT_MOST【表示子布局限制在一个最大值内,一般为WARP_CONTENT】

MeasureSpec.UNSPECIFIED【表示子布局想要多大就多大,很少使用】

对模式进行判断,如果模式是EXACTLY,说明用户在布局文件中对宽高设定了具体数值或者match_parent,如果是具体数值,那么就将这个具体数值作为最终宽高,如果是match_parent,就将其填充父布局。如果是AT_MOST,说明用户在布局文件中设定为了wrap_content,那么就应该将我们的默认宽度或者高度作为最终的宽高
通过以上测量,就能够在设定为wrap_content时依然能够限定在一定的大小中。


另外,如果想为自定义控件加上一些点击事件之类的效果,我们还可以:

1.让自定义类实现OnClickListener



2.在构造方法中注册监听事件

Android深入浅出自定义控件(一)_第1张图片

3.实现onClick方法

Android深入浅出自定义控件(一)_第2张图片


运行并点击,就会发现文字内容每点击一次就+1


希望本文能够帮助你解决自定义View的困惑,关于ViewGroup和重写系统控件的见解会在以后的博文进行整理

更多相关文章

  1. Android Layout布局文件里的android:layout_height等属性不起作
  2. Android Native 绘图方法
  3. Android中 android:layout_weight 属性
  4. Android 虚化图片的方法
  5. Android中自定义属性基本步骤
  6. android完全退出程序的方法
  7. android常用方法汇总-更新中

随机推荐

  1. android 命令大全
  2. 在Android中创建启动界面 编辑
  3. android 中theme.xml与style.xml的区别
  4. Android基础知识总结(转载)
  5. 修改android Launcher
  6. android 传统蓝牙开发 (附示例源码)
  7. (2) 搭建 Android(安卓)系统开发环境
  8. Android(安卓)SDK源码提取Python脚本(4.0
  9. Android五种存储方式
  10. Android(安卓)实现滚动数字的TextView