如果按照类型划分的话,自定义View的实现方式大概有三种,自绘控件、组合控件、继承控件;
这篇主要实现的是自绘控件:http://write.blog.csdn.net/postedit/51533987
创建自定义View的步骤:
(1)继承View或者View的派生子类:
必须提供能够获取Context和作为属性的AttributeSet类型对象的构造函数,当View从XML布局中创建之后,XML标签中的所有属性都从资源包中读取出来并且作为一个AttributeSet传递给View的构造函数;
我们来看看View派生类:
- public class TimerCountDownView extends View{
- public Paint paint;
- public Rect rect;
- public int count = 10;
- public TimerCountDownView(Context context) {
- super(context);
- }
- public TimerCountDownView(Context context, AttributeSet attrs) {
- super(context, attrs);
- paint = new Paint(Paint.ANTI_ALIAS_FLAG);//使位图抗锯齿
- rect = new Rect();//创建矩形
- }
- public TimerCountDownView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
- }
我们来说说这三种构造函数什么时候会得到调用:
第一个:一般View view = new View(context);的时候被调用;
第二个:当我们在xml布局文件中使用View时,会在inflate布局时调用,如:
<View layout_width="match_parent" layout_height="match_parent"/>。
第三种:跟第二种类似,但是增加Style属性设置
<View style="@styles/MyCustomStyle" layout_width="match_parent" layout_height="match_parent"/>。
因为本示例我们是要在xml中使用自定义View,所以第二个构造函数是必须需要的;
(2)定义自定义属性:
一个良好的自定义控件应该是通过xml来进行控制的,所以我们应该考虑下我们自定义的View中哪些属性可以被提取到xml中,通常是在资源元素<declare-styleable>中为View定义自定义属性,需要在项目中添加<declare-styleable>资源,这些资源存放在res/values/attrs.xml文件中,其中attrs这个文件的名字可以是任意的;
- <resources>
- <declare-styleable name="TimerCountDownView">
- <attr name="rectColor" format="color"/>
- <attr name="paintColor" format="color"/>
- <attr name="textSize" format="integer"/>
- </declare-styleable>
- </resources>
一旦定义了自定义属性,我们便可以在xml文件中进行使用,唯一区别就是需要引入我们自定义的命名空间,引入方式有两种:
一种是在根布局中进行命名空间的声明,在根布局中添加xmlns:http://schemas.android.com/apk/res/[你的自定义View所在的包路径]
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:myview="http://schemas.android.com/apk/res-auto"<!-xmlns:myview="http://schemas.android.com/apk/com.hzw.myownviewtest"-->
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <com.hzw.myownviewtest.TimerCountDownView
- android:id="@+id/view"
- android:layout_width="50dp"
- android:layout_height="50dp"
- myview:rectColor="#FFE4E1"
- myview:paintColor="#F0FFFF"
- myview:textSize="30"
- android:layout_centerInParent="true"/>
- </RelativeLayout>
可能你的编译器会报:No resource identifier found for attribute 'paintColor' in package这个错误,解决方法是将命名空间的路径修改为xmlns:http://schemas.android.com/apk/res-auto,原因在于ADT升级之后,系统会对自定义控件的命名空间的路径进行优化;
另一种是在自定义View上定义命名空间:
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <com.hzw.myownviewtest.TimerCountDownView
- xmlns:myview="http://schemas.android.com/apk/res-auto"<!-xmlns:myview="http://schemas.android.com/apk/com.hzw.myownviewtest"-->
- android:id="@+id/view"
- android:layout_width="50dp"
- android:layout_height="50dp"
- myview:rectColor="#FFE4E1"
- myview:paintColor="#F0FFFF"
- myview:textSize="30"
- android:layout_centerInParent="true"/>
- </RelativeLayout>
在上面,我们已经通过xml设定了自定义属性rectColor和paintColor,当View从xml布局中创建之后,xml标签中的所有属性都从资源包中读取出来并作为AttributeSet传递给了View的构造函数,尽管我们可以从AttributeSet中直接读取出里面的属性值,但是这样做有一些缺点,比如带有值的资源引用没有进行处理、样式并没有得到允许之类的,那么为了想要提取属性,我们就需要用到TypedArray类和obtainStyledAttributes方法,将AttributeSet传递给obtainStyledAttributes方法,这个方法会返回一个TypedArray数组,有了这个数组我们就可以获取里面的属性啦;
- public int rectColor;
- public int paintColor;
- public int textSize;
- public TimerCountDownView(Context context) {
- super(context);
- }
- public TimerCountDownView(Context context, AttributeSet attrs) {
- super(context, attrs);
- paint = new Paint(Paint.ANTI_ALIAS_FLAG);//使位图抗锯齿
- rect = new Rect();//创建矩形
- setupAttributes(attrs);
- }
- public TimerCountDownView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
- /**
- * 读取我们自定义的属性值
- * @param attrs
- */
- public void setupAttributes(AttributeSet attrs)
- {
- //提取自定义属性到TypeArray对象中
- TypedArray ta = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.TimerCountDownView, 0, 0);
- try {
- rectColor = ta.getColor(R.styleable.TimerCountDownView_rectColor, Color.BLACK);
- paintColor = ta.getColor(R.styleable.TimerCountDownView_paintColor, Color.BLACK);
- textSize = ta.getInteger(R.styleable.TimerCountDownView_textSize, 20);
- }finally {
- //TypeArray对象是共享的,必须被重复利用
- ta.recycle();
- }
- }
(4)添加属性的setter和getter方法:
属性是用来控制View的行为以及外观的,但是只有在View初始化之后,属性才可读,为了提供动态的行为,需要暴露每个自定义属性的一对getter和setter方法:
- public int getRectColor() {
- return rectColor;
- }
- public void setRectColor(int rectColor) {
- this.rectColor = rectColor;
- invalidate();
- requestLayout();
- }
- public int getPaintColor() {
- return paintColor;
- }
- public void setPaintColor(int paintColor) {
- this.paintColor = paintColor;
- invalidate();
- requestLayout();
- }
- public int getTextSize() {
- return textSize;
- }
- public void setTextSize(int textSize) {
- this.textSize = textSize;
- invalidate();
- requestLayout();
- }
;
(5)自定义绘制(onDraw(canvas))
绘制视图最重要的一步就是重写onDraw方法了,这个方法有一个Canvas类型的参数,Canvas可以理解成你想画什么,Paint可以理解成你想怎么画,比如想要画一个矩形,那么Canvas用于决定你所画矩形的大小位置等,而Paint用于决定你所画矩形的背景色等;
好了,下面我们通过自定义一个倒计时控件来运用一下上面介绍的步骤:
首先是我们的自定义View----TimerCountDownView,他继承自View:
- public class TimerCountDownView extends View{
- public Paint paint;
- public Rect rect;
- public int count = 10;
- public int rectColor;
- public int paintColor;
- public int textSize;
- public TimerCountDownView(Context context) {
- super(context);
- }
- public TimerCountDownView(Context context, AttributeSet attrs) {
- super(context, attrs);
- paint = new Paint(Paint.ANTI_ALIAS_FLAG);//使位图抗锯齿
- rect = new Rect();//创建矩形
- setupAttributes(attrs);
- }
- public TimerCountDownView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
- /**
- * 读取我们自定义的属性值
- * @param attrs
- */
- public void setupAttributes(AttributeSet attrs)
- {
- //提取自定义属性到TypeArray对象中
- TypedArray ta = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.TimerCountDownView, 0, 0);
- try {
- rectColor = ta.getColor(R.styleable.TimerCountDownView_rectColor, Color.BLACK);
- paintColor = ta.getColor(R.styleable.TimerCountDownView_paintColor, Color.BLACK);
- textSize = ta.getInteger(R.styleable.TimerCountDownView_textSize, 20);
- }finally {
- //TypeArray对象是共享的,必须被重复利用
- ta.recycle();
- }
- }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- paint.setColor(rectColor);//设置背景色
- canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
- paint.setColor(paintColor);
- paint.setTextSize(textSize);//设置字体大小
- String text = String.valueOf(count);
- paint.getTextBounds(text, 0, text.length(), rect);
- float textWidth = rect.width();
- float textHeight = rect.height();
- canvas.drawText(text, getWidth()/2-textWidth/2, getHeight()/2+textHeight/2, paint);
- }
- /**
- * 设置View上面需要显示的值
- * @param count
- */
- public void setCount(int count)
- {
- this.count = count;
- invalidate();//表示重绘制View
- }
- public int getRectColor() {
- return rectColor;
- }
- public void setRectColor(int rectColor) {
- this.rectColor = rectColor;
- invalidate();
- requestLayout();
- }
- public int getPaintColor() {
- return paintColor;
- }
- public void setPaintColor(int paintColor) {
- this.paintColor = paintColor;
- invalidate();
- requestLayout();
- }
- public int getTextSize() {
- return textSize;
- }
- public void setTextSize(int textSize) {
- this.textSize = textSize;
- invalidate();
- requestLayout();
- }
- }
第30行的setupAttributes方法用于获取我们自定义的属性值,首先通过obtainStyledAttributes获取到我们在xml文件中设置的自定义属性数组,注意获取到这些值的前提条件是对于我们的自定义属性,必须提供其对应的set和get方法,也就是从68到96行的部分,注意在每个属性的set方法中需要调用invalidate来进行View的重绘,调用requestLayout来进行视图的重绘,获取我们自定义属性方法的第二个参数是假如我们不设置某个属性类型的话,默认的属性值,比如第35行的ta.getColor方法,第二个参数Color.BLACK指的是当我们在xml中不设置rectColor属性的情况下,rectColor的默认值是Color.BLACK,注意在第40行,使用完TypeArray之后要对其进行回收处理;
第45行的onDraw用于在View上面绘制矩形,比较简单;
接下来就是我们的MainActivity了:
- public class MainActivity extends Activity {
- public Handler mHandler;
- public int count = 10;
- public TimerCountDownView myView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- myView = (TimerCountDownView) findViewById(R.id.view);
- TimeThread thread = new TimeThread();
- thread.start();
- mHandler = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case 0:
- count = msg.getData().getInt("count");
- myView.setCount(count);
- break;
- default:
- break;
- }
- }
- };
- }
- class TimeThread extends Thread
- {
- @Override
- public void run() {
- while(true)
- {
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- count = count - 1;
- if(count == 0)
- count = 10;
- Message message = new Message();
- Bundle bundle = new Bundle();
- bundle.putInt("count", count);
- message.setData(bundle);
- message.what = 0;
- mHandler.sendMessage(message);
- }
- }
- }
- }