前言:Canvas,是Android绘图机制的核心api,可以绘制出矩形、圆形、贝塞尔曲线、路径、文字等等各种图形,在Android的自定义View中需要大量用到这个类。现实生活中,我们在纸上画出一幅画,需要三样东西:画笔、画板、纸张。而Canvas就相当于画板,至于为什么不是纸张,咱们后面再说~
Paint,画笔,绘图三要素之一。常用api如下:
//初始化画笔
paint = new Paint();
//设置抗锯齿
paint.setAntiAlias(true);
//设置画笔宽度
paint.setStrokeWidth(5);
//设置画笔颜色
paint.setColor(Color.RED);
//设置画笔透明度
paint.setAlpha(128);
//设置画笔样式
paint.setStyle(Paint.Style.STROKE);
经过这样设置的Paint对象,已经可以用来绘制各种各样的图形了,当然还有许多其它的api,不是很常用,这里就不做介绍。
画板,我们在自定义View的时候,通常会重写其中的onDraw()方法:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
这个方法默认提供了一个Canvas对象,通常我们会在自定义View的构造方法中去初始化Paint对象,然后配合提供的Canvas对象,就可以在自定义View的时候绘去制各种各样的图形。然而,这是重写View的方法时候默认提供的,如果我们想自己去初始化Canvans对象该怎么弄呢?也很简单:
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
为啥要传入一个Bitmap对象呢?刚我们说现实生活中画一幅画的三要素是画板、画笔、纸张,画笔和画板都有了,而Bitmap就是最后一个要素:纸张。用以存储Canvas绘制的各种图形。Canvas有空参数的构造方法:
/**
* Construct an empty raster canvas. Use setBitmap() to specify a bitmap to
* draw into. The initial target density is {@link Bitmap#DENSITY_NONE};
* this will typically be replaced when a target bitmap is set for the
* canvas.
*/
public Canvas() {
if (!isHardwareAccelerated()) {
// 0 means no native bitmap
mNativeCanvasWrapper = nInitRaster(null);
mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
this, mNativeCanvasWrapper);
} else {
mFinalizer = null;
}
}
重点不看代码,看它的注释,Construct an empty raster canvas. Use setBitmap() to specify a bitmap to draw into。翻译过来就是:构造一个空的Canvas,使用setBitmap()方法指定一个Bitmap对象用于画入内容。意思就是,即便你使用空参数的构造方法,也需要调用setBitmap方法为其指定一个Bitmap对象,用于存储画出来的内容。以后你用Canvas的api画出来的各种图形,都是作用在你指定的Bitmap对象上的。 可以指定任意的Bitmap。好吧,这里也顺带说一下 Bitmap.Config这个类,createBitmap时传入的第三个参数,它是一个枚举类,用于决定图片存储的质量和大小。
每个像素信息只存储了alpha这一项信息。
public enum Config {
//每个像素只存储透明度信息
ALPHA_8
//存储R、G、B信息三原色的信息。Red 5位;Green 6位;Blue 5位,5+6+5=16位=2个字节。
RGB_565
//存储透明度和三原色的信息。Alpha 4位;Red 4位;Green 4位;Blue 4位,4+4+4+4=16位=2个字节
ARGB_4444
//存储透明度和三原色的信息。Alpha 8位;Red 8位;Green 8位;Blue 8位,8+8+8+8=32位=4个字节
ARGB_8888
//图片以8个字节存储
RGBA_F16
}
其实就是指定了每个像素的A、R、G、B所占的位数,合起来就是一个像素点所占的大小,可以认为每个像素点所占的大小越大,图片的质量越高,官方推荐使用ARGB_8888。举个例子,一张图片的分辨率是19201080,采用的是ARGB_8888格式,则它在内存中占用的大小是19201080*4/1000/1000=8.29M,这个值已经很大了,Android为每个应用分配的内存默认是16M,不同的厂商可能会修改这个值,但是也架不住这样的内存使用率。这就又涉及到图片的内存优化了,本篇博客重点不在这里,感兴趣的小伙伴可以自行百度Android图片内存优化相关的文章。好了,我们要开始画画了~
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置画笔
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(ContextCompat.getColor(getContext(), R.color.colorAccent));
paint.setAntiAlias(true);
//画圆
canvas.drawCircle(getWidth()/2, getHeight()/2, 100, paint);
}
第一个参数是圆心坐标点的x值,第二个参数是圆心左边的y值,第三个参数是画笔,坐标相对于View的左上角。
//画矩形
canvas.drawRect(100,100,300,300,paint);
前两个参数是矩形左上角的坐标,后两个参数是矩形右下角的坐标,最后一个是画笔对象。除了这种方法,还能直接传入一个Rect或者RectF对象:
//画矩形
//RectF rect = new RectF(100,100,300,300);
Rect rect = new Rect(100,100,300,300);
canvas.drawRect(rect,paint);
效果是一样的,Rect和RectF都表示一个矩形的区域,功能都一样,不同的是Rect中的参数是整型的,RectF中的参数是单精度浮点型的。效果如下:
//画圆角矩形
RectF rect = new RectF(100,100,300,300);
canvas.drawRoundRect(rect,10,10,paint);
Rect和Paint对象自不用说,中间的两个参数是啥呢?分别是矩形外接椭圆的x轴和y轴的半径,这么说不好理解:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/colorAccent"/>
<corners android:radius="5dp"></corners>
</shape>
类似于上面代码中的radius属性。效果嘛,如下:
当然还有个重载的方法,把RectF对象拆成两个坐标点:
canvas.drawRoundRect(100,100,300,300,10,10,paint);
效果是一样的。
//绘制椭圆
RectF rect = new RectF(100,100,400,300);
canvas.drawOval(rect,paint);
为何要传入一个矩形的对象,这涉及到高中的数学知识了,已知一个矩形,其内切椭圆的值是唯一的,所以可以根据矩形来绘制出唯一的内切椭圆。
注意:如果矩形是正方形的话,则绘制出来椭圆的是圆形。
弧线来自于圆或者椭圆,而椭圆来自于矩形内切,所以绘制弧线依旧需要一个矩形作为参数:
public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,Paint paint)
每个参数的意义如下:
注:角度的定义参考高中数学象限知识。
好吧,画一个试试呗:
//绘制弧线
RectF rect = new RectF(100,100,400,300);
canvas.drawArc(rect,0,45,true,paint);
效果如下:
从0度开始,顺时针扫过45度,绘制出来的弧形(或者说扇形),如果把paint的style设置成Style.STROKE,图形还是一样的,只不过不填充了
如果不绘制弧边:
//绘制弧线,不显示弧边
RectF rect = new RectF(100, 100, 400, 300);
canvas.drawArc(rect, 0, 45, false, paint);
效果是这样的:
好吧,这个api既可以绘制扇形,也可以绘制一小段弧线,通过useCenter参数来控制即可。
public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
举例:
canvas.drawLine(30,30,100,100,paint);
效果:
绘制多条直线:
public void drawLines(float[] pts, Paint paint)
注:float数组中两个元素确定一个点,四个元素确定一条直线,元素个数可以是任意个数,少于四个元素则无法绘制任何直线,大于四个元素,则可以绘制 (int)(元素个数/4) 条直线。
举例:
float[] pts = {100, 100, 200,200,300,300,500,500};
canvas.drawLines(pts,paint);
效果
还有个重载的方法:
public void drawLines(float[] pts, int offset, int count,Paint paint)
举个例子:
float[] pts = {
100, 100, 200, 200,
300, 300, 400, 400,
500, 500, 600, 600
};
canvas.drawLines(pts,4,4,paint);
这里有三条直线,跳过最前面四个元素,且实际参与绘制的数组元素个数为4,则数组中只有{300, 300, 400, 400}四个元素参与到绘制了,所以只会绘制中间一条直线。
假设一个自定义View的大小是100px*100px,如何去设置整个控件的颜色呢?当然,可以设置一个背景,也可以通过View自身的onDraw方法去绘制控件自身的颜色。
canvas.drawARGB(255,189,60,100);
四个值分别是A、R、G、B。
也可以使用drawColor()方法指定颜色:
canvas.drawColor(Color.parseColor("#FFBD3C64"));
效果是一模一样的。
Canvas还有许多其它的api,如drawBitmap、drawPicture、drawText等等,这几个api涉及的东西比较多,这篇博客一时写不完,有时间会另写一篇博客介绍,文中如果有错误或者理解不到位的地方,欢迎小伙伴批评指正,完~
下一篇:Android绘图篇——绘制文本