View 上的内容是通过 Canvas 绘制出来的,但 Canvas 中的大多数绘制方法都是需要 Paint 作为参数的,例如 canvas.drawCircle(100, 100, 50, paint) 最后就需要传递一个 Paint。
内容概览
内部类
类型 | 简介 |
---|---|
enum | Paint.Cap Cap指定了描边线和路径(Path)的开始和结束显示效果。 |
enum | Paint.Join Join指定线条和曲线段在描边路径上连接的处理。 |
enum | Paint.Style Style指定绘制的图元是否被填充,描边或两者均有(以相同的颜色)。 |
常量
类型 | 简介 |
---|---|
int | ANTI_ALIAS_FLAG 开启抗锯齿功能的标记。 |
int | DITHER_FLAG 在绘制时启用抖动的标志。 |
int | FILTER_BITMAP_FLAG 绘制标志,在缩放的位图上启用双线性采样。 |
构造方法
构造方法 | 摘要 |
---|---|
Paint() | 使用默认设置创建一个新画笔。 |
Paint(int flags) | 创建一个新画笔并提供一些特殊设置(通过 flags 参数)。 |
Paint(Paint paint) | 创建一个新画笔,并使用指定画笔参数初始化。 |
公开方法
画笔介绍
由于画笔需要控制的内容也相当的多,因此它内部包含了相当多的属性变量,配置起来也相当繁杂,不过比较好的是,画笔会提供一套默认设置来供我们使用。例如,创建一个新画笔,这个新画笔已经默认设置了绘制颜色为黑色,绘制模式为填充。
画笔基本设置
要使用画笔就要会创建画笔,创建一个画笔是非常简单的,它有三种创建方法,如下:
// 1.创建一个默认画笔,使用默认的配置
Paint()
// 2.创建一个新画笔,并通过 flags 参数进行配置。
Paint(int flags)
// 3.创建一个新画笔,并复制参数中画笔的设置。
Paint(Paint paint)
第1种方式创建默认画笔的方式
第2种方式如果设置 flags 为 0 创建出来和默认画笔也是相同的,至于 flags 参数可用设置哪些内容,可以参考最上面的常量表格,里面的参数都是可以设置的,如果需要设置多个参数,参数之间用 | 进行连接即可。如下:
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
第3种方式是根据已有的画笔复制一个画笔,就是将已有画笔的所有属性都复制到新画笔种,也比较容易理解:
Paint paintCopy = new Paint(paint);
复制后的画笔是一个全新的画笔,对复制后的画笔进行任何修改调整都不会影响到被复制的画笔。
你可以观察下面的测试代码来了解以上的3种创建方式。
Paint paint1 = new Paint();
Log.i(TAG, "paint1 isAntiAlias = " + paint1.isAntiAlias());
Log.i(TAG, "paint1 isDither = " + paint1.isDither());
Paint paint2 = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
Log.i(TAG, "paint2 isAntiAlias = " + paint2.isAntiAlias());
Log.i(TAG, "paint2 isDither = " + paint2.isDither());
Paint paint3 = new Paint(paint2);
paint3.setAntiAlias(false);
Log.i(TAG, "paint3 isAntiAlias = " + paint3.isAntiAlias());
Log.i(TAG, "paint3 isDither = " + paint3.isDither());
输出结果:
paint1 isAntiAlias = false
paint1 isDither = false
paint2 isAntiAlias = true
paint2 isDither = true
paint3isAntiAlias = false
paint3 isDither = true
画笔在创建之后依旧可以调整,上面的第2种和第3种创建方式,进行的参数设置,在画笔创建完成后依旧可以进行。通过如下的方法:
返回值 | 简介 |
---|---|
int | getFlags() 获取画笔相关的一些设置(标志)。 |
void | setFlags(int flags) 设置画笔的标志位。 |
void | set(Paint src) 复制 src 的画笔设置。 |
void | reset() 将画笔恢复为默认设置。 |
不过并不建议使用 setFlags 方法,这是因为 setFlags 方法会覆盖之前设置的内容,例如:
Paint paint = new Paint();
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
paint.setFlags(Paint.DITHER_FLAG);
Log.i(TAG, "paint isAntiAlias = " + paint.isAntiAlias());
Log.i(TAG, "paint isDither = " + paint.isDither());
输出结果:
paint isAntiAlias = false
paint isDither = true
从结果可以看出,只有最后一次设置的内容有效,之前设置的所有内容都会被覆盖掉。因此不推荐使用。
如果了解 Google 工程师比较喜欢的编码规范就可以知道原因是什么,最终的flags是由多个flag用”或(|)”连接起来的,也就是一个变量,如果直接使用 set 方法,自然是会覆盖掉之前设置的内容的。如果想要调整 flag 个人建议还是使用 paint 提供的一些封装方法,如:setDither(true),而不要自己手动去直接操作 flag。
如果有人对 flags 的存储方式感兴趣可以看看这个例子,假如:0x0001 表示类型A, 0x0010 表示类型B,0x0100 表示类型C,0x1000 表示类型D,那么当类型ABD同时存在,但C不存在时时只用存储 0x1011 即可,相比于使用4个 boolean 值来说,这种方案可以显著的节省内存空间的占用,并且用户设置起来也比较方便,可以使用或”|“同时设置多个类型。当然弊端也是有的,那就是单独更改其中一个参数时时稍微麻烦一点,需要进行一些位运算。
使用 set(Paint src) 可以复制一个画笔,但需要注意的是,如果调用了这个方法,当前画笔的所有设置都会被覆盖掉,而替换为所选画笔的设置。
如果想将画笔重置为初始状态,那就调用 reset() 方法,该方法会让画笔的所有设置都还原为初始状态,调用该方法后得到的画笔和刚创建时的状态是一样的。
画笔颜色
这个是最常用的方法,它相关的方法如下;
返回值 | 简介 |
---|---|
int | getAlpha() 只返回颜色的alpha值。 |
void | setAlpha(int a) 设置透明度。 |
int | getColor() 返回画笔的颜色。 |
void | setColor(int color) 设置颜色。 |
void | setARGB(int a, int r, int g, int b) 设置带透明通道的颜色。 |
Android 中有 1 个透明通道(Alpha)和 3 个色彩通道(RGB),其中 Alpha 通道可以单独设置。通过 getAlpha() 和 setAlpha(int a) 方法可以单独调整透明通道,其中 setAlpha(int a) 中参数的取值范围是 0 - 255,即对应 16 进制中的 0x00 - 0xFF。
// 下面两种设置方式是等价的,一种是 10 进制,一种是 16 进制
paint1.setAlpha(204);
paint2.setAlpha(0xCC);
同理,setARGB(int a, int r, int g, int b) 的 4 个参数的取值范围也是 0 - 255,对应 0x00 - 0xFF,下面的设置同样是等价的。
paint1.setARGB(204, 255, 255, 0);
paint2.setARGB(0xCC, 0xFF, 0xFF, 0x00);
当然,这样设置起来比较麻烦,我们最常用的还是直接使用 setColor(int color) 方法,它接受一个 int 类型的参数来表示颜色,我们既可以使用系统内置的一些标准颜色,也可以使用自定义的一些颜色,如下:
paint.setColor(Color.GREEN);
paint.setColor(0xFFE2A588);
在使用 setColor 方法时,所设置的颜色必须是 ARGB 同时存在的,通常每个通道用两位16进制数值表示,如 0xFFE2A588。总共 8 位,其中 FF 表示 Alpha 通道。
如果不设置 Alpha 通道,则默认Alpha通道为 0,即完全透明,如:0xE2A588,总共 6 位,没有 Alpha 通道,如果这样设置,则什么颜色也绘制不出来。
同样需要注意的是,setColor 不能直接引用资源,不能这样使用:paint.setColor(R.color.colorPrimary); 如果你这样使用了,编译器会报错的。如果想要使用预定义的颜色资源,可以像下面这样调用:
int color = context.getResources().getColor(R.color.colorPrimary);
paint.setColor(color);
画笔宽度
画笔宽度,就是画笔的粗细,它通过下面的方式设置。
// 将画笔设置为描边
paint.setStyle(Paint.Style.STROKE);
// 设置线条宽度
paint.setStrokeWidth(120);
注意: 这条线的宽度是同时向两边进行扩展的,例如绘制一个圆时,将其宽度设置为 120 则会向外扩展 60 ,向内缩进 60,如下图所示。
因此如果绘制的内容比较靠近视图边缘,使用了比较粗的描边的情况下,一定要注意和边缘保持一定距离(边距>StrokeWidth/2) 以保证内容不会被剪裁掉。
如下面这种情况,直接绘制一个矩形,如果不考虑画笔宽度,则绘制的内容就可能不正常。
在一个 1000x1000 大小的画布上绘制与个大小为 500x500 ,宽度为 100 的矩形。
灰色部分为画布大小。
红色为分割线,将画笔分为均等的四份。
蓝色为矩形。
paint.setStrokeWidth(100);
paint.setColor(0xFF7FC2D8);
Rect rect = new Rect(0, 0, 500, 500);
canvas.drawRect(rect, paint);
如果考虑到画笔宽度,需要绘制一个大小刚好填充满左上角区域的矩形,那么实际绘制的矩形就要小一些,(如果只是绘制一个矩形的话,可以将矩形向内缩小画笔宽度的一半) 这样绘制出来就是符合预期的。
paint.setStrokeWidth(100);
paint.setColor(0xFF7FC2D8);
Rect rect = new Rect(0, 0, 500, 500);
rect.inset(50, 50); // 注意这里,向内缩小半个宽度
canvas.drawRect(rect, paint);
这里只是用矩形作为例子说明,事实上,绘制任何图形,只要有描边的,就要考虑描边宽度占用的空间,需要适当的缩小图形,以保证其可以完整的显示出来。
注意:在实际的自定义 View 中也不要忽略 padding 占用的空间哦。
hairline mode (发际线模式):
在设置画笔宽度的的方法有如下注释:
Set the width for stroking.
Pass 0 to stroke in hairline mode.
Hairlines always draws a single pixel independent of the canva's matrix.
在画笔宽度为 0 的情况下,使用 drawLine 或者使用描边模式(STROKE)也可以绘制出内容。只是绘制出的内容始终是 1 像素,不受画布缩放的影响。该模式被称为hairline mode (发际线模式)。
如果你设置了画笔宽度为 1 像素,那么如果画布放大到 2 倍,1 像素会变成 2 像素。但如果是 0 像素,那么不论画布如何缩放,绘制出来的宽度依旧为 1 像素。
// 缩放 5 倍
canvas.scale(5, 5, 500, 500);
// 0 像素 (Hairline Mode)
paint.setStrokeWidth(0);
paint.setColor(0xFF7FC2D8);
canvas.drawCircle(500, 455, 40, paint);
// 1 像素
paint.setStrokeWidth(1);
paint.setColor(0xFF7FC2D8);
canvas.drawCircle(500, 545, 40, paint);
在画笔宽度为 0 的情况下,使用 drawLine 或者使用描边模式(STROKE)也可以绘制出内容。只是绘制出的内容始终是 1 像素,不受画布缩放的影响。该模式被称为hairline mode (发际线模式)。
如果你设置了画笔宽度为 1 像素,那么如果画布放大到 2 倍,1 像素会变成 2 像素。但如果是 0 像素,那么不论画布如何缩放,绘制出来的宽度依旧为 1 像素。
// 缩放 5 倍
canvas.scale(5, 5, 500, 500);
// 0 像素 (Hairline Mode)
paint.setStrokeWidth(0);
paint.setColor(0xFF7FC2D8);
canvas.drawCircle(500, 455, 40, paint);
// 1 像素
paint.setStrokeWidth(1);
paint.setColor(0xFF7FC2D8);
canvas.drawCircle(500, 545, 40, paint);
可以看到,在放大 5 倍的情况下,1 像素已经变成了 5 像素,但 hairline mode 绘制出来依旧是 1 像素。
画笔模式
这里的画笔模式(Paint.Style)就是指绘制一个图形时,是绘制边缘轮廓,绘制内容区域还是两者都绘制,它有三种模式。
Style | 简介 |
---|---|
Paint.Style.FILL | 填充内容,也是画笔的默认模式。 |
Paint.Style.STROKE | 描边,只绘制图形轮廓。 |
Paint.Style.FILL_AND_STROKE | 描边+填充,同时绘制轮廓和填充内容。 |
//填充
mPaint.setStyle(Paint.Style.FILL);
// 描边
mPaint.setStyle(Paint.Style.STROKE);
// 描边+填充
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
示例程序:
用一个简单的例子说明一下不同模式的区别。
// 画笔初始设置
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(50);
paint.setColor(0xFF7FC2D8);
// 填充,默认
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(500, 200, 100, paint);
// 描边
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(500, 500, 100, paint);
// 描边 + 填充
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(500, 800, 100, paint);
画笔线帽
画笔线帽(Paint.Cap)用于指定线段开始和结束时的效果。
// 它通过下面方式设置
paint.setStrokeCap(Paint.Cap.ROUND);
Android 中有三种线帽可供选择。
Cap | 简介 |
---|---|
Paint.Cap.BUTT | 无线帽,也是默认类型。 |
Paint.Cap.SQUARE | 以线条宽度为大小,在开头和结尾分别添加半个正方形。 |
Paint.Cap.ROUND | 以线条宽度为直径,在开头和结尾分别添加一个半圆。 |
我们用以下代码来测试线帽。
// 画笔初始设置
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
paint.setStrokeWidth(80);
float pointX = 200;
float lineStartX = 320;
float lineStopX = 800;
float y;
// 默认
y = 200;
canvas.drawPoint(pointX, y, paint);
canvas.drawLine(lineStartX, y, lineStopX, y, paint);
// 无线帽(BUTT)
y = 400;
paint.setStrokeCap(Paint.Cap.BUTT);
canvas.drawPoint(pointX, y, paint);
canvas.drawLine(lineStartX, y, lineStopX, y, paint);
// 方形线帽(SQUARE)
y = 600;
paint.setStrokeCap(Paint.Cap.SQUARE);
canvas.drawPoint(pointX, y, paint);
canvas.drawLine(lineStartX, y, lineStopX, y, paint);
// 圆形线帽(ROUND)
y = 800;
paint.setStrokeCap(Paint.Cap.ROUND);
canvas.drawPoint(pointX, y, paint);
canvas.drawLine(lineStartX, y, lineStopX, y, paint);
注意:
画笔默认是无线帽的,即 BUTT。
Cap 也会影响到点的绘制,在 Round 的状态下绘制的点是圆的。
在绘制线条时,线帽时在线段外的,如上图红色部分所显示的内容就是线帽。
上图中红色的线帽是用特殊方式展示出来的,直接绘制的情况下,线帽颜色和线段颜色相同。
2.6 线段连接方式(拐角类型)
画笔的连接方式(Paint.Join)是指两条连接起来的线段拐角显示方式。
// 通过下面方式设置连接类型
paint.setStrokeJoin(Paint.Join.ROUND);
它同样有三种样式:
Cap | 简介 |
---|---|
Paint.Join.MITER | 尖角 (默认模式) |
Paint.Join.BEVEL | 平角 |
Paint.Join.ROUND | 圆角 |
![]() |
通过效果图可以看出几种不同模式的补偿规则。
2.7 斜接模式长度限制
Android 中线段连接方式默认是 MITER,即在拐角处延长外边缘,直到相交位置。
根据数学原理我们可知,如果夹角足够小,接近于零,那么交点位置就会在延长线上无限远的位置。 为了避免这种情况,如果连接模式为 MITER(尖角),当连接角度小于一定程度时会自动将连接模式转换为 BEVEL(平角)。
那么多大的角度算是比较小呢?根据资料显示,这个角度大约是 28.96°,即 MITER(尖角) 模式下小于该角度的线段连接方式会自动转换为 BEVEL(平角) 模式。
我们可以通过下面的方法来更改默认限制:
// 设置 Miter Limit,参数并不是角度
paint.setStrokeMiter(10);