canvas可以绘制很多几何图形、文本等很多东西:
1. drawText
2. drawPoint
3. drawLine
4. drawRect
5. drawCircle
6. drawOval
7. drawArc
8. drawPath
9. drawBitmap
关于绘图基础讲解这篇博客讲的非常细致,推荐大家可以去看一下, Android中Canvas绘图基础详解(附源码下载),我这里就不重复讲述了。
Canvas作为绘制图像的直接对象,提供了几个非常有用的方法:
1. Canvas.save()
2. Canvas.restore()
3. Canvas.translate()
4. Canvas.rotate()
canvas.save()这个方法可以理解为保存画布,将之前所有已经绘制的图像保存下来,让后续的操作在新的画布上进行操作,旋转、移动不影响原来的操作,canvas.restore()就是将sava()之后绘制的所有图像与save()之前绘制的图像进行合并。后面两个方法就是将画布平移和旋转了,理解成将坐标系旋转和平移更加恰当,初看没有什么用,其实在我们绘图当中还是有很大作用的,比如画时钟,画仪表盘,通过旋转等很轻松就可以画出刻度线,避免复杂的三角函数,我们通过一个画一个简单的时钟小例子来看一下canvas的运用:
private Paint mPaint;
private Paint mTextPaint;
private Paint mHourPaint;
private Paint mMinutePaint;
private Paint mSecondPaint;
private Paint mPointPaint;
private int mTotalWidth;
private int mTotalHeight;
//秒针长度
private float secondPointerLength;
//分针长度
private float minutePointerLength;
//时针长度
private float hourPointerLength;
//指针反向超过圆点的长度
private static final float POINT_BACK_LENGTH = 40f;
//长刻度线
private static final float LONG_DEGREE_LENGTH = 40f;
//短刻度线
private static final float SHORT_DEGREE_LENGTH = 20f;
//圆的半径
private float radius;
我们首先绘制一个圆形:
//先画一个位于屏幕中央、半径为屏幕宽度一半的圆
radius = mTotalWidth / 2 - mPaint.getStrokeWidth() / 2;
canvas.drawCircle(mTotalWidth / 2, mTotalHeight / 2, radius, mPaint);
然后绘制刻度线:
//接着画刻度线,整点的刻度线长一点和粗一点
for (int i = 0; i < 60; i++) {
if(i % 5 == 0){
mPaint.setStrokeWidth(5);
canvas.drawLine(radius, mTotalHeight / 2 - radius, radius, mTotalHeight / 2 - radius + LONG_DEGREE_LENGTH, mPaint);
}else{
mPaint.setStrokeWidth(3);
canvas.drawLine(radius, mTotalHeight / 2 - radius, radius, mTotalHeight / 2 - radius + SHORT_DEGREE_LENGTH, mPaint);
}
//设置旋转角度,以圆心为原点旋转
canvas.rotate(6, mTotalWidth / 2, mTotalHeight / 2);
}
画一根线段还是非常简单的,确定两个端点的坐标就可以了,如果要把时钟上所有的刻度线画出来并确定所有的坐标点,后面的坐标并不是简单的通过坐标加减就能实现的,必须要通过角度计算三角函数才能实现,比如我们计算刻度为1的刻度线坐标:
//圆的半径
float r = getWidth() / 2 - paint.getStrokeWidth() / 2;
//绘制一个圆
canvas.drawCircle(getWidth() / 2, getHeight() / 2, r, paint);
paint.setColor(Color.BLUE);
canvas.save();
//坐标系移动到圆心
canvas.translate(getWidth() / 2, getHeight() / 2);
//绘制1的刻度点
canvas.drawLine((float)((r-20) * Math.sin(30*Math.PI/180)), -(float)((r-20) * Math.cos(30*Math.PI/180)), (float)(r * Math.sin(30*Math.PI/180)), -(float)(r * Math.cos(30*Math.PI/180)), paint);
canvas.restore();
画出来大概就是这个样子:
这样就是要算好角度,比起旋转要麻烦得多,有时候还是要善于利用一些方法,事半功倍。这里大家要注意一点的是,我们canvas的坐标系有且只有一个,坐标原点在View的左上角,从坐标原点向右为x轴的正半轴,从坐标原点向下为y轴的正半轴。坐标系移动到圆点,那么还是以原点向右为x轴的正半轴,原点向下为y轴的正半轴,但是角度是与Y轴的负半轴的夹角,比如说刻度1的30度就是与Y轴的负半轴的夹角,计算坐标的时候算出来的Y轴就是负的,X轴还是正的,这里要搞清楚这一点,不然这个坐标你是计算不准确的,不理解的可以画一个图看一下。
下面我们就画数字,数字不通过旋转来画,当然旋转也是可以画出来的,这样主要是画的好看一点,我们通过过计算坐标来画,先写一个方法计算坐标:
//计算线段的起始坐标
private float[] calculatePoint(float angle,float length){
float[] points = new float[4];
if(angle <= 90f){
points[0] = -(float) Math.sin(angle*Math.PI/180) * POINT_BACK_LENGTH;
points[1] = (float) Math.cos(angle*Math.PI/180) * POINT_BACK_LENGTH;
points[2] = (float) Math.sin(angle*Math.PI/180) * length;
points[3] = -(float) Math.cos(angle*Math.PI/180) * length;
}else if(angle <= 180f){
points[0] = -(float) Math.cos((angle-90)*Math.PI/180) * POINT_BACK_LENGTH;
points[1] = -(float) Math.sin((angle-90)*Math.PI/180) * POINT_BACK_LENGTH;
points[2] = (float) Math.cos((angle-90)*Math.PI/180) * length;
points[3] = (float) Math.sin((angle-90)*Math.PI/180) * length;
}else if(angle <= 270f){
points[0] = (float) Math.sin((angle-180)*Math.PI/180) * POINT_BACK_LENGTH;
points[1] = -(float) Math.cos((angle-180)*Math.PI/180) * POINT_BACK_LENGTH;
points[2] = -(float) Math.sin((angle-180)*Math.PI/180) * length;
points[3] = (float) Math.cos((angle-180)*Math.PI/180) * length;
}else if(angle <= 360f){
points[0] = (float) Math.cos((angle-270)*Math.PI/180) * POINT_BACK_LENGTH;
points[1] = (float) Math.sin((angle-270)*Math.PI/180) * POINT_BACK_LENGTH;
points[2] = -(float) Math.cos((angle-270)*Math.PI/180) * length;
points[3] = -(float) Math.sin((angle-270)*Math.PI/180) * length;
}
return points;
}
//保存当前画布
canvas.save();
//设置文字大小,绘制刻度数字
mTextPaint.setTextSize(30);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setFakeBoldText(true);
for (int i = 0; i < 12; i++) {
String degree = (i + 1) + "";
float[] temp = calculatePoint((i + 1) * 30, radius - mTextPaint.getTextSize() / 2 - LONG_DEGREE_LENGTH - 10);
canvas.drawText(degree, temp[2],temp[3] + mTextPaint.getTextSize() / 2, mTextPaint);
}
下面我们就绘制时针分针
Calendar calendar = Calendar.getInstance();
float[] hourPoint = calculatePoint(calendar.get(Calendar.HOUR_OF_DAY) % 12 / 12f * 360, hourPointerLength);
float[] minutePoint = calculatePoint(calendar.get(Calendar.MINUTE) / 60f * 360, minutePointerLength);
float[] secondPoint = calculatePoint(calendar.get(Calendar.SECOND) / 60f * 360, secondPointerLength);
//绘制时针
canvas.drawLine(hourPoint[0], hourPoint[1], hourPoint[2],hourPoint[3], mHourPaint);
//绘制分针
canvas.drawLine(minutePoint[0], minutePoint[1], minutePoint[2], minutePoint[3], mMinutePaint);
//绘制秒针
canvas.drawLine(secondPoint[0], secondPoint[1], secondPoint[2], secondPoint[3], mSecondPaint);
这样画时针分针我们就可以通过获取系统时间根据角度计算出线段的起始坐标,就可以让时钟动起来了,当然还是可以通过旋转来实现的:
Calendar calendar = Calendar.getInstance();
float[] hourPoint = calculatePoint(0, hourPointerLength);
float[] minutePoint = calculatePoint(0, minutePointerLength);
float[] secondPoint = calculatePoint(0, secondPointerLength);
//绘制时针
canvas.save();
canvas.rotate(calendar.get(Calendar.HOUR_OF_DAY) % 12 / 12f * 360, 0, 0);
canvas.drawLine(hourPoint[0], hourPoint[1], hourPoint[2],hourPoint[3], mHourPaint);
canvas.restore();
//绘制分针
canvas.save();
canvas.rotate(calendar.get(Calendar.MINUTE) / 60f * 360, 0, 0);
canvas.drawLine(minutePoint[0], minutePoint[1], minutePoint[2], minutePoint[3], mMinutePaint);
canvas.restore();
//绘制秒针
canvas.save();
canvas.rotate(calendar.get(Calendar.SECOND) / 60f * 360, 0, 0);
canvas.drawLine(secondPoint[0], secondPoint[1], secondPoint[2], secondPoint[3], mSecondPaint);
canvas.restore();
我们在画一个圆心,就OK了
canvas.drawCircle(0, 0, 2, mPointPaint);
下面通过一个线程来不断使他重绘:
private Handler handler = new Handler(){
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
invalidate();
handler.sendEmptyMessageDelayed(0, 1000);
break;
default:
break;
}
};
};
然后在初始化的时候发送一个消息给handler让指针运动就好了
//启动线程让指针运动
handler.sendEmptyMessage(0);
现在这个圆大功告成
canvas也是2D绘图的基础,不过掌握好对我们自定义view有着指导性的作用。
其实canvas可以绘制各种图标,简单灵活,可以说是非常强大,各位可以下去多多练习,我在github上看到了一篇关于canvas绘图的图标库十分强大,有兴趣的可以去看看,https://github.com/xcltapestry/XCL-Charts,非常不错的一个库。