1.概述
大家在做自定义控件时,可以把自己想像成一名艺术家。你在创作自己的艺术品。那么作为一名画家,你肯定得需要至少两样工具:画笔和画布。这两样是作画的基础,缺一不可。那么Android有这两样东西吗,答案是肯定的。在Android中Paint就是我们的画笔,Canvas就是我们的画布。那么这两样东西该如何去用呢?其实也很简单,Paint提供了很多方法,我们通过这些方法可以对这只笔进行设置,比如笔的颜色,画出来线条的粗细等等。而Canvas则负责具体要画的东西,比如点,线,矩形,圆形等等
2.准备工具
public class TimeView extends View {
private Context mContext;
private Paint mPaint;
public TimeView(Context context) {
super(context);
this.mContext = context;
initPaint();
}
public TimeView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
initPaint();
}
/**
* 初始化画笔
*/
private void initPaint() {
mPaint = new Paint();
//抗锯齿
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(0);
}
@Override
protected void onDraw(Canvas canvas) {
//画具体内容
}
3.画边框
我们的边框就是一个简单的圆:
@Overrideprotected
void onDraw(Canvas canvas) {
//圆形边框
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 3, mPaint);
}
我们可以看到,要想画一个圆,只用调用canvas的drawCircle(float x,float y,float radius,Paint mPaint)方法,它接受四个参数,其中想x、y为圆的圆心。这里我要说一下Android的坐标系,它的坐标原点默认在屏幕的左上角,向右为为X轴正方向,向下为Y轴正方向
这里我们圆心选在控件的中心,即宽高的一半。第三个参数是圆的半径,这里我们就取控件宽的三分之一,第四个为之前我们创建的画笔。我们来运行看一下效果:
怎么样还不错吧,总算有点东西了,这里我们的Style设的是Paint.Style.STROKE,我们换成Paint.Style.FILL试下:
可以看到圆内被填充了,这下你应该知道FILL和STROKE的区别了吧。好了我们来看下一个
3.画中心点
有了外面的边框我们还可以再给它一个中心点,当然你觉得没必要,不加也可以。不过我们还是来看一下在Android中是如何画一个点的。其实也很简单,你只需调用
canvas.drawPoint(float x,float y,Paint mPaint)
方法,我想这方法也不用在过多的解释了,x,y为中心点的坐标,mPaint为之前的画笔。
4.画刻度线
时钟自然是少不了刻度线啦,所以我们来看看刻度线是如何画的。刻度线说白了就是一条条的直线。那么在Canvas
中有画直线的方法吗?答案是必须的。画布给我们提供一个叫canvas.drawLine(float fromX,float fromY,float stopX,float stopY,Paint mPaint)
的方法;我想大家应该在初中就知道两点决定一条直线,所以这个方法中一二两个参数分别为起始点的x、y坐标,三四两个参数为终点坐标,第五个自然为我们的画笔啦。好了有了这个方法,只要求出起点坐标和终点坐标,理论上我们能画出任意的直线。不过这里可能有人要坐不住了:你扯独自呢,这么多刻度线,怎么求啊?确实,这么多刻度线,要想一条一条求出起点坐标和终点坐标,确实不太现实。那么有没有简单点的方法呢?先别急,在回答这个问题之前我们先来看一下Canvas
的操作坐标系的几个方法:
canvas.translate(float x,float y);
canvas.rotate(float degree);
canvas.rotate(float degree,float x,float y);
这里我简单说一下这几个方法,第一个是坐标系的平移,传入的两个参数,分别为平移后坐标原点的X、Y坐标,说白了就是你想把坐标原点移到哪个点就传入哪个点;第二个方法是把坐标系旋转一定角度,传入正数则顺时针转,负数则相反。第三个方法是绕着传入的(X,Y)点旋转一定度数。好了,知道了这几个方法现在再画刻度线是不是有点思路了呢。我们知道,要想求出所有刻度的起始与终点坐标很复杂,也不太现实。但求一条刻度的坐标还是好求的。为了坐标表示方便我们移动一下坐标系,即调用
canvas.translate(getWidth()/2,getHeight()/2)
将坐标原点移到圆心处。
如上图所示,我们把坐标原点移到圆心,这样如果我们要画图中绿色刻度线,其实就很简单了。起始坐标和终点坐标的Y轴坐标均为0,起始坐标的X轴坐标为半径减去刻度线长度,而终点坐标的X轴坐标就是半径。怎么样,这样画一条刻度线是不是挺简单的,相信你一定能画好。好,接下来我们再画一条,不过在画之前,我们得做一个小小的动作,就是把坐标系旋转一下。如下图:
我们把原来的红色坐标系顺时针旋转了a角度得到了黄色坐标系,也就是调用了canvas.rotate(a)
,我们之前说过顺时针转,要传入正值,所以这里的a
是一个正数。好了,这样我们再来求一下黄色X’轴上的刻度线,会发现它的坐标和第一条刻度线的坐标是一样的。是不是问题变得很简单了。这样不管你要画几条刻度线,不管你想画在哪,只要旋转你的坐标系,而不用反复的计算刻度线的坐标。比如,我们都知道圆是360度,你想每隔一度,就画一条刻度线,那么你就每次旋转一度,然后画一条线。这样不断循环后,就画出了360条刻度线。当然你可以根据自己的需求画任意条。代码如下:
@Overrideprotected
void onDraw(Canvas canvas) {
//圆形边框
mPaint.setStrokeWidth(2);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 3, mPaint);
//圆心 mPaint.setStrokeWidth(5); canvas.drawPoint(getWidth() / 2, getHeight() / 2, mPaint);
//设置刻度线线宽
mPaint.setStrokeWidth(1);
//将坐标原点移到圆心处
canvas.translate(getWidth() / 2, getHeight() / 2);
for (int i = 0; i < 360; i++) {
//这里刻度线长度我设置为25
canvas.drawLine(getWidth() / 3 - 25, 0, getWidth() / 3, 0, mPaint);
canvas.rotate(1);
}
}
效果如下:我是每隔1度画了一条刻度线。为便于观看,我放大了整个图片,可以看到我们的刻度线分布的还是很均匀、整齐的。
当然如果你觉得刻度线的长度都一样长,太单调了你也可以进行适当的改变。比如你可以每秒钟设置一个中等长度,每五秒钟设置一个最长的长度,然后其他的刻度线都设置一个最小的长度。我们知道圆是360度,并且秒针转一圈为60秒,所以一秒就对应360度/60秒=6度,那么五秒也就是5*6 = 30度。得到这两个关键的角度我们就可以写代码了:
@Override
protected void onDraw(Canvas canvas) {
//圆形边框
mPaint.setStrokeWidth(2);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 3, mPaint);
//圆心
mPaint.setStrokeWidth(5);
canvas.drawPoint(getWidth() / 2, getHeight() / 2, mPaint);
//设置刻度线线宽
mPaint.setStrokeWidth(1);
//将坐标原点移到圆心处
canvas.translate(getWidth()/2,getHeight()/2);
for (int i = 0; i < 360; i++) {
//这里刻度线长度我设置为25
canvas.drawLine(getWidth() / 3-25, 0,getWidth() /3, 0, mPaint);
canvas.rotate(1);
}
}
效果如下:
4.画数字
接下来我们在时钟上画上1-12的数字,有关写字Canvas给我提供了这样一个方法:drawText(String text,float x,float y,Paint mPaint)
;其中text指我们要写的字,mPaint是我们的画笔,那么x,y是什么呢?很显然x和y是用来给文字定位用的,x指的文字最左边的X坐标,那么y呢,难道是文字最下边的Y坐标吗。其实不是的。我们来看下图:
上图给出个文字的一些尺寸参数,我们可以看到其中那条黑线,即Baseline,上文的y其实就是这条线的Y坐标。Baseline到文字顶部距离叫做ascent,Baseline到文字底部叫做descent,我们知道一般文字上部和下部会有一点padding,所以top和bottom的距离会略大于ascent,descent。如果有两行文字,那么上一行的descent到下一行的ascent的距离就叫做leading,即行间距。那么我们如何能得到这些参数呢。其实很简单,在调用drawText方法之前,我们一般会通过mPaint.setTextSize(float size);来设置字体大小,设完以后,我们就可以通过mPaint.getFontMetrics()方法来得到一个Paint.FontMetrics对象,这个对象封装了上述我们要的文字尺寸信息。代码如下:
Paint mPaint = new Paint();
mPaint.setTextSize(50);
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
float ascent = fontMetrics.ascent;
float bottom = fontMetrics.bottom;
float descent = fontMetrics.descent;
float leading = fontMetrics.leading;
float top = fontMetrics.top;
注意:上述这些参数大小与具体是什么文字无关,只与字体大小和字体格式有关。并且,在Baseline上方的尺寸为负,下方为正。也就是top、ascent都是负数,bottom和descent为正数。
好了,知道了如何写文字后,我们就可以在时钟上写上我们要的十二个数字了,一共12个数字,一个圆360度,所以每个30度写一个字。这样我们就可以用之前的方法,没写完一个数,就将坐标系旋转30度。代码如下:
mPaint.setTextSize(25);
mPaint.setStyle(Paint.Style.FILL);
Rect textBound = new Rect();//创建一个矩形
for (int i = 0; i <12; i++) {
if (i == 0){
//将文字装在上面创建的矩形中,即这个矩形就是文字的边框
mPaint.getTextBounds(12+"",0,(12+"").length(),textBound);
canvas.drawText(12+"",-textBound.width()/2,-(getWidth()/3-50),mPaint);
canvas.rotate(30);
}else{
mPaint.getTextBounds(i+"",0,(i+"").length(),textBound);
canvas.drawText(i+"",-textBound.width()/2,-(getWidth()/3-50),mPaint);
canvas.rotate(30);
}
}
上面的代码还是好理解的,我们创建一个循环,每循环一次就写个文字,并且将坐标系顺时针旋转30度,其中我们可以看到,我们创建了一个矩形,然后我们调用mPaint.getTextBounds(String text,int start,int end,Rect textBound)将文字的边框存入其中,这个方法传入四个参数,第一个为我们要画的字符串,第二三个参数分别为这个字符串的开始角标和结束叫角标,最后一个为矩形。这样我们就可以把这个矩形理解为这个字符串的边框,有了边框我们就可以知道这个字符串的很多参数,比如上下左右的坐标,以及字符串的宽高等。这样当我们画数字时,它的X坐标就是文字宽度的一半,注意别忘了负号。好了我们来看下效果如何:
没错正如你所料,虽然数字是有了,而且还挺整齐的,不过文字也跟着旋转了。看来简单的旋转坐标系是不行了。那还有其他办法吗,有的人可能会说了,直接算出每个数字的具体坐标然后在画。这样当然可以,只要你够耐心,而三角函数还不错的话,可以尝试下。不过我还是劝你不要这么干,因为这样计算既麻烦而且算的准确度也不高。那么还有什么更好的办法呢。这里我想到了一个好办法,可以给大家参考一下。其实我们每一次画数字的时候可以提取出一个动作,举个例子,比如我们要画数字“1”,如下图所示,我们知道“12”和“1”之间为30度,那么我们可以先将图中黑色坐标系顺时针旋转30度,得到蓝色坐标系,然后我们将蓝色坐标系沿着Y轴反方向移动合适的距离,得到红色坐标系,然后再将坐标系逆时针转30度得到绿色坐标系,我们的目标就是在绿色坐标系的中心画上数字,具体怎么画,我想也不用多说了。画完后,再将坐标系原路返回。也就是,将绿色坐标系顺时针旋转30度,回到红色坐标系,然后将红色坐标系沿着Y轴正方向移动和之前平移时同样的距离,得到蓝色坐标系,最后将蓝色坐标系逆时针旋转30度回到原来的黑色坐标,即刚开始的坐标系。这样经过一系列的动作,画完一个数字,我们的坐标系还是和原来没画数字时的一样。这样我们就可把这一系列动作写成一个方法,在每次画数字之前调用它就行。
这系列动作我们可以写成如下方法:
private void drawNum(Canvas canvas, int degree, String text, Paint paint) {
Rect textBound = new Rect();
paint.getTextBounds(text, 0, text.length(), textBound);
canvas.rotate(degree);
canvas.translate(0, 50 - getWidth() / 3);//这里的50是坐标中心距离时钟最外边框的距离,当然你可以根据需要适当调节
canvas.rotate(-degree);
canvas.drawText(text, -textBound.width() / 2,
textBound.height() / 2, paint);
canvas.rotate(degree);
canvas.translate(0, getWidth() / 3 - 50);
canvas.rotate(-degree);
}
这个方法,我们传入四个参数,分别为画布,要画数字与12点之间的夹角,要画的数字以及画笔。接下来,在我们每次画数字是调用这个方法就行了:
mPaint.setTextSize(25);
mPaint.setStyle(Paint.Style.FILL);
for (int i = 0; i < 12; i++) {
if (i == 0) {
drawNum(canvas, i * 30, 12 + "", mPaint);
} else {
drawNum(canvas, i * 30, i + "", mPaint);
}
}
代码还是挺直观的,我就不过多解释了。我们来看一下效果:
只能用两字形容“完美”。
3.画指针
好了,数字也总算画好了,接下来就只剩下指针了,指针分秒针、分针和时针,知道一种怎么画就可以。其实很简单,这里我直接调用drawLine()方法,代码如下:
//秒针
//秒针
canvas.save()
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(2);
//其实坐标点(0,0)终点坐标(0,-190),这里的190为秒针长度
canvas.drawLine(0, 0, 0,
-190, mPaint);
canvas.restore();
//分针
canvas.save();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(4);
canvas.rotate(30);
canvas.drawLine(0, 0, 0,
-130, mPaint);
canvas.restore();
//时针
canvas.save();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(7);
canvas.rotate(90);
canvas.drawLine(0, 0, 0,
-90, mPaint);
canvas.restore();
因为我们每个指针的旋转角度都不同,所以为了避免相互影响,我们把每个指针画在canvas.save()和canvas.restore()之间,相当每个指针都画在不同的图层上,最后合并为一张图。
http://www.cnblogs.com/zxxiaoxia/archive/2015/05/16/4508400.html
网友评论