自己动手,丰衣足食 —— 学习自定义View(一)
2016-04-25 18:13
295 查看
自己动手,丰衣足食 —— 学习自定义View(一)
前言
我们经常在做项目的时候遇到这样的情况,客户提出需求,UI把设计稿拿出来,你发现直接用现成的开源库好像不行哎,多多少少有些不同。这时候你就会想:要是能自己画一个出来就好了。所以说:自己写出来的View最靠谱,最灵活,你想让它什么样它就是什么样,前提是你要能画出来…
这也是为什么自定义View的知识如此重要的原因了,所以,打算系统地总结下这方面的知识。
首先,我们先由一个最简单的同心圆开始,例子简单,主要是归纳下自定义View的步骤,为以后更复杂的View打下基础。
好了,废话少说,先看一下这次View的效果,见下图:
(图1)
这次我们要完成的就是上图中的带进度条的同心圆部分。
自定义View的大致步骤:
自定义属性;创建自定义View的类,继承自View或其它继承自View的控件;
添加类的构造函数(在构造函数中,我们要获取之前自定义的属性);
重写父类的一些方法,例如onDraw,OnMeasure等.
下面结合代码具体来看一下:
1.在res/values下面新建一个attrs.xml,在里面我们可以定义一些View要用到属性。
有人就问了,这些属性有什么用呢?举个例子,我们平时用到的控件有许多属性,这样我们可以根据实际需要控制它的长宽,颜色等等,这样可以提高View的重用性,避免频繁修改代码。首先确认一下本次需要要到的属性:
/** * 圆环的颜色 */ private int roundColor; /** * 圆环进度的颜色 */ private int roundProgressColor; /** * 内圆的颜色 */ private int roundInsideColor; /** * 进度百分比的字符串的颜色 */ private int textProgressColor; /** * 标题文字的字符串的颜色 */ private int textTitleColor; /** * 标题文字的字符串 */ private String textTitle; /** * 进度百分比的字符串的字体 */ private float textProgressSize; /** * 标题文字的字符串的字体 */ private float textTitleSize; /** * 圆环的宽度 */ private float roundWidth; /** * 最大进度 */ private int max; /** * 当前进度 */ private int progress; /** * 是否显示中间的进度 */ private boolean textIsDisplayable; /** * 进度的风格,实心或者空心 */ private int style; public static final int STROKE = 0; public static final int FILL = 1;
然后根据上面的内容写出attrs.xml:
<?xml version="1.0" encoding="UTF-8"?> <resources> <declare-styleable name="RoundProgressBar"> <attr name="roundColor" format="color"/> <attr name="roundProgressColor" format="color"/> <attr name="roundInsideColor" format="color"/> <attr name="roundWidth" format="dimension"></attr> <attr name="textProgressSize" format="dimension" /> <attr name="textProgressColor" format="color" /> <attr name="textTitle" format="string" /> <attr name="textTitleSize" format="dimension" /> <attr name="textTitleColor" format="color" /> <attr name="max" format="integer"></attr> <attr name="style"> <enum name="STROKE" value="0"></enum> <enum name="FILL" value="1"></enum> </attr> </declare-styleable> ... </resources>
declare-styleable 标签后的name是此styleable的名字,attr 标签后的name是属性的名字,format类似于值的类型,具体可查看这个链接。
2.创建一个自定义View的类,它必须继承自View或者其它继承自View的控件。
... public class RoundProgressBar extends View { public RoundProgressBar(Context context) { this(context, null); } public RoundProgressBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); ... } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); ... } ... }
可以看到,我们创建好以后,重写了它的三个构造方法,并且重写了onDraw这个方法,至于干什么我们下一步再说。
3.在构造方法中获取自定义的属性。
我们知道,构造方法是在这个类对象刚被创建的时候被调用的,那么这3个构造方法我们需要用哪个呢?第一个是在正常创建类对象时被调用的:
RoundProgressBar roundProgressBar = new RoundProgressBar(this);
第二个是在xml里添加一个View:
<com.customview.RoundProgressBar android:layout_width="120dp" android:layout_height="120dp" />
里面添加的属性会被存放在AttributeSet参数里。
前两个方法都会在某些情况下被系统自动调用,而第三个方法则需要我们实现调用。而且由代码中可以看出,第三个方法是被第二个方法调用,第二个方法又是被第一个方法调用。所以,我们只要在第三个方法中获取自定义属性,就能确保自定义属性的获取。关于第三个构造方法的其它高级技巧,这里就不再深入了。
public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); ... TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar); //获取自定义属性和默认值 roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundColor, Color.RED); roundProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundProgressColor, Color.GREEN); roundInsideColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundInsideColor, Color.TRANSPARENT); textTitle = mTypedArray.getString(R.styleable.RoundProgressBar_textTitle); textProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_textProgressColor, Color.GREEN); textTitleColor = mTypedArray.getColor(R.styleable.RoundProgressBar_textTitleColor, Color.GREEN); textProgressSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_textProgressSize, 15); textTitleSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_textTitleSize, 15); roundWidth = mTypedArray.getDimension(R.styleable.RoundProgressBar_roundWidth, 5); max = mTypedArray.getInteger(R.styleable.RoundProgressBar_max, 100); textIsDisplayable = mTypedArray.getBoolean(R.styleable.RoundProgressBar_textIsDisplayable, true); style = mTypedArray.getInt(R.styleable.RoundProgressBar_style, 0); mTypedArray.recycle(); }
可以看到,我们先是调用context.obtainStyledAttributes(attrs,R.styleable.RoundProgressBar)来获取TypedArray,然后从TypedArray获取我们定义的属性。
另外,获取属性的方法,第一个参数就是我们自定义的属性,第二个参数是一个默认值。
4.接下来,得到了属性值,我们就可以在onDraw里把自定义View描画出来了。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /** * 画外层的圆环 */ int centre = getWidth() / 2; //获取圆心的x坐标 int radius = (int) (centre - roundWidth / 2); //圆环的半径 int radiusInside = (int) (centre - roundWidth); //内圆的半径 paint.setColor(roundColor); //设置圆环的颜色 paint.setStyle(Paint.Style.STROKE); //设置空心 paint.setStrokeWidth(roundWidth); //设置圆环的宽度 paint.setAntiAlias(true); //消除锯齿 canvas.drawCircle(centre, centre, radius, paint); //画出圆环 /** * 画内层的圆 */ paint.setColor(roundInsideColor); paint.setStyle(Paint.Style.FILL); paint.setStrokeWidth(radiusInside); paint.setAntiAlias(true); canvas.drawCircle(centre, centre, radiusInside, paint); /** * 画进度百分比 */ paint.setStrokeWidth(0); paint.setColor(textProgressColor); paint.setTextSize(textProgressSize); paint.setTypeface(Typeface.DEFAULT_BOLD); //设置字体 int percent = (int) (((float) progress / (float) max) * 100); //中间的进度百分比,先转换成float在进行除法运算,不然都为0 float textWidth = paint.measureText(percent + "%"); //测量字体宽度,我们需要根据字体的宽度设置在圆环中间 if (textIsDisplayable && percent != 0 && style == STROKE) { canvas.drawText(percent + "%", centre - textWidth / 2, centre - radiusInside/3 + textProgressSize / 2, paint); //画出进度百分比 } /** * 画标题 */ paint.setStrokeWidth(0); paint.setColor(textTitleColor); paint.setTextSize(textTitleSize); paint.setTypeface(Typeface.DEFAULT_BOLD); //设置字体 textWidth = paint.measureText(textTitle); //测量字体宽度,我们需要根据字体的宽度设置在圆环中间 if (textIsDisplayable && style == STROKE) { canvas.drawText(textTitle, centre - textWidth / 2, centre + radiusInside/3 + textProgressSize / 2, paint); //画出进度百分比 } /** * 画圆弧 ,画圆环的进度 */ //设置进度是实心还是空心 paint.setStrokeWidth(roundWidth); //设置圆环的宽度 paint.setColor(roundProgressColor); //设置进度的颜色 RectF oval = new RectF(centre - radius, centre - radius, centre + radius, centre + radius); //矩形用于定义的圆弧的形状和大小的界限 switch (style) { case STROKE: { paint.setStyle(Paint.Style.STROKE); canvas.drawArc(oval, 0, 360 * progress / max, false, paint); //根据进度画圆弧 break; } case FILL: { paint.setStyle(Paint.Style.FILL_AND_STROKE); if (progress != 0) canvas.drawArc(oval, 0, 360 * progress / max, true, paint); //根据进度画扇形 break; } } }
首先要说明一下关于View的坐标系知识,View相对于整个屏幕左上角的位置坐标叫做绝对坐标,而相对于父控件的位置坐标叫做相对坐标,以下如不说明,使用的都是相对坐标。
关于绘图的具体细节就不深入了,感兴趣的同学可以自己画图算下坐标和长度以及相关的API。
这样,这个自定义的RoundProgressBar 就算是写好了,那么我们怎么在自己写的代码里使用它呢?
跟平常一样的xml布局文件,添加一个RoundProgressBar :
... xmlns:custom="http://schemas.android.com/apk/res-auto" ... <com.customview.RoundProgressBar android:id="@+id/rpb" android:layout_width="120dp" android:layout_height="120dp" android:layout_gravity="center_horizontal" android:layout_marginTop="31dp" custom:roundColor="@color/light_green" custom:roundProgressColor="@color/yellow" custom:roundInsideColor="@color/blue" custom:roundWidth="17dp" custom:textProgressSize="20sp" custom:textProgressColor="@color/white" custom:textTitle="当前全市拥堵率" custom:textTitleSize="8sp" custom:textTitleColor="@color/white" custom:textIsDisplayable="true" />
可以看到,我们在布局文件顶部添加了一个命名空间,这样我们就可以使用自定义属性的前缀“custom”了,后面跟那些属性,就像系统默认的“android”一样。
好了,关于自定义View的基础知识就介绍到这里,下一次我们深入研究一下它的其它特性。
参考文章:
http://blog.csdn.net/xiaanming/article/details/10298163
http://shaohui.xyz/2016/07/08/Android%E8%87%AA%E5%AE%9A%E4%B9%89view%E8%AF%A6%E8%A7%A3/
http://www.jianshu.com/p/e76374706c3e?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
http://blog.csdn.net/lmj623565791/article/details/24252901
相关文章推荐
- android app性能优化大汇总(google官方Android性能优化典范 - 第2季)
- 51nod1066bash游戏
- C/C++获取当前系统时间
- java实现发送邮件工具
- 20160425待整理
- [spring源码学习]三、IOC源码——自定义配置文件读取
- java实现发送邮件工具
- 绘制圆形以及球体
- wireshark抓包工具常用筛选命令方法
- B. Kuriyama Mirai's Stones
- 机器学习实践指南(一)—— 总论
- c++的输入与输出,/r与/n的区别
- 不能验证这个“安装 OS X EI Capitan”应用程序副本解决方法(已修正)
- redis之数据安全与性能保障
- UVA_11100_The Trip, 2007
- Linux 重启Tomcat脚本
- springmvc 常用的注解
- 有用
- SIGPIPE导致进程终止
- 手机端输入6位密码