Android自定义View-Paint

View 上的内容是通过 Canvas 绘制出来的,但 Canvas 中的大多数绘制方法都是需要 Paint 作为参数的,例如 canvas.drawCircle(100, 100, 50, paint) 最后就需要传递一个 Paint。

内容概览

内部类

类型简介
enumPaint.Cap Cap指定了描边线和路径(Path)的开始和结束显示效果。
enumPaint.Join Join指定线条和曲线段在描边路径上连接的处理。
enumPaint.Style Style指定绘制的图元是否被填充,描边或两者均有(以相同的颜色)。

常量

类型简介
intANTI_ALIAS_FLAG 开启抗锯齿功能的标记。
intDITHER_FLAG 在绘制时启用抖动的标志。
intFILTER_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种创建方式,进行的参数设置,在画笔创建完成后依旧可以进行。通过如下的方法:

返回值简介
intgetFlags() 获取画笔相关的一些设置(标志)。
voidsetFlags(int flags) 设置画笔的标志位。
voidset(Paint src) 复制 src 的画笔设置。
voidreset() 将画笔恢复为默认设置。

不过并不建议使用 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() 方法,该方法会让画笔的所有设置都还原为初始状态,调用该方法后得到的画笔和刚创建时的状态是一样的。

画笔颜色

这个是最常用的方法,它相关的方法如下;

返回值简介
intgetAlpha() 只返回颜色的alpha值。
voidsetAlpha(int a) 设置透明度。
intgetColor() 返回画笔的颜色。
voidsetColor(int color) 设置颜色。
voidsetARGB(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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的代码家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值