android-----自定义View实现系列(一)
2016-05-30 17:31
459 查看
在android开发中,我们和View打交道的机会是最多的,那么问题来了,平常你所使用的View基本上都是系统自带的,如果某一天项目中需要一些特殊的View控件的话,你就不得不实现自己的View了,那么你至少就需要了解View到底是怎么绘制、怎么显示的了,鉴于View的绘制源码比较难懂,接下来的几篇博客,我会首先从应用的角度来实现一些自定义View的小Demo,之后简单分析下源码,能力有限,望有不对的地方赐教,小弟不胜感激;
如果按照类型划分的话,自定义View的实现方式大概有三种,自绘控件、组合控件、继承控件;
这篇主要实现的是自绘控件:http://write.blog.csdn.net/postedit/51533987
创建自定义View的步骤:
(1)继承View或者View的派生子类:
必须提供能够获取Context和作为属性的AttributeSet类型对象的构造函数,当View从XML布局中创建之后,XML标签中的所有属性都从资源包中读取出来并且作为一个AttributeSet传递给View的构造函数;
我们来看看View派生类:
我们来说说这三种构造函数什么时候会得到调用:
第一个:一般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这个文件的名字可以是任意的;
一旦定义了自定义属性,我们便可以在xml文件中进行使用,唯一区别就是需要引入我们自定义的命名空间,引入方式有两种:
一种是在根布局中进行命名空间的声明,在根布局中添加xmlns:http://schemas.android.com/apk/res/[你的自定义View所在的包路径]
可能你的编译器会报:No resource identifier found for attribute 'paintColor' in package这个错误,解决方法是将命名空间的路径修改为xmlns:http://schemas.android.com/apk/res-auto,原因在于ADT升级之后,系统会对自定义控件的命名空间的路径进行优化;
另一种是在自定义View上定义命名空间:
在上面,我们已经通过xml设定了自定义属性rectColor和paintColor,当View从xml布局中创建之后,xml标签中的所有属性都从资源包中读取出来并作为AttributeSet传递给了View的构造函数,尽管我们可以从AttributeSet中直接读取出里面的属性值,但是这样做有一些缺点,比如带有值的资源引用没有进行处理、样式并没有得到允许之类的,那么为了想要提取属性,我们就需要用到TypedArray类和obtainStyledAttributes方法,将AttributeSet传递给obtainStyledAttributes方法,这个方法会返回一个TypedArray数组,有了这个数组我们就可以获取里面的属性啦;
(4)添加属性的setter和getter方法:
属性是用来控制View的行为以及外观的,但是只有在View初始化之后,属性才可读,为了提供动态的行为,需要暴露每个自定义属性的一对getter和setter方法:
;
(5)自定义绘制(onDraw(canvas))
绘制视图最重要的一步就是重写onDraw方法了,这个方法有一个Canvas类型的参数,Canvas可以理解成你想画什么,Paint可以理解成你想怎么画,比如想要画一个矩形,那么Canvas用于决定你所画矩形的大小位置等,而Paint用于决定你所画矩形的背景色等;
好了,下面我们通过自定义一个倒计时控件来运用一下上面介绍的步骤:
首先是我们的自定义View----TimerCountDownView,他继承自View:
第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了:
点击下载源码!!!!!
如果按照类型划分的话,自定义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>我们为每一个attr结点都写了name和format属性,format是name属性所对应的数据结构,合法的值包括string,color,dimension,boolean,integer,float,enum;
一旦定义了自定义属性,我们便可以在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>(3)应用自定义属性:
在上面,我们已经通过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(); }当View的属性发生变化之后我们需要重绘View以及重新布局,一定要确保调用了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); } } } }我们采用Handler来每隔500ms更新矩形上面显示的值达到倒计时的目的,代码相对简单,不做过多解释;
点击下载源码!!!!!
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories