Paint类
Paint 的 API 大致可以分为 4 类:
- 颜色
- 效果
- drawText() 相关
- 初始化
颜色
1.基本颜色
像素的基本颜色,根据绘制内容的不同而有不同的控制方式: Canvas 的颜色填充类方法 drawColor/RGB/ARGB() 的颜色,是直接写在方法的参数里,通过参数来设置的; drawBitmap() 的颜色,是直接由 Bitmap 对象来提供的;除此之外,是图形和文字的绘制,它们的颜色就需要使用 paint 参数来额外设置了。
Paint 设置颜色的方法有两种:一种是直接用 Paint.setColor/ARGB() 来设置颜色,另一种是使用 Shader 来指定着色方案。
直接设置颜色
setColor(int color)
setARGB(int a, int r, int g, int b)
setShader(Shader shader) 设置 Shader
Shader 这个英文单词很多人没有见过,它的中文叫做「着色器」,也是用于设置绘制颜色的。「着色器」不是 Android 独有的,它是图形领域里一个通用的概念,它和直接设置颜色的区别是,着色器设置的是一个颜色方案,或者说是一套着色规则。当设置了 Shader 之后,Paint 在绘制图形和文字时就不使用 setColor/ARGB() 设置的颜色了,而是使用 Shader 的方案中的颜色。
在 Android 的绘制里使用 Shader ,并不直接用 Shader 这个类,而是用它的几个子类。具体来讲有 LinearGradient RadialGradient SweepGradient BitmapShader ComposeShader 这么几个:
LinearGradient 线性渐变
设置两个点和两种颜色,以这两个点作为端点,使用两种颜色的渐变来绘制颜色
设置了 Shader 之后,绘制出了渐变颜色的圆。(其他形状以及文字都可以这样设置颜色,我只是没给出图。)
注意:在设置了 Shader 的情况下, Paint.setColor/ARGB() 所设置的颜色就不再起作用。
x0 y0 x1 y1:渐变的两个端点的位置
color0 color1 是端点的颜色
tile:端点范围之外的着色规则,类型是 TileMode。TileMode 一共有 3 个值可选: CLAMP, MIRROR 和 REPEAT。CLAMP (夹子模式???)会在端点之外延续端点处的颜色;MIRROR 是镜像模式;REPEAT 是重复模式。具体的看一下例子就明白。
RadialGradient 辐射渐变
SweepGradient 扫描渐变
BitmapShader
ComposeShader 混合着色器
所谓混合,就是把两个 Shader 一起使用。
注意:上面这段代码中我使用了两个 BitmapShader 来作为 ComposeShader() 的参数,而 ComposeShader() 在硬件加速下是不支持两个相同类型的 Shader 的,所以这里也需要关闭硬件加速才能看到效果。
具体来说, PorterDuff.Mode 一共有 17 个,可以分为两类:
- Alpha 合成 (Alpha Compositing)
- 混合 (Blending)
2.setColorFilter(ColorFilter colorFilter)
ColorFilter 这个类,它的名字已经足够解释它的作用:为绘制设置颜色过滤。颜色过滤的意思,就是为绘制的内容设置一个统一的过滤策略,然后 Canvas.drawXXX() 方法会对每个像素都进行过滤后再绘制出来。
在 Paint 里设置 ColorFilter ,使用的是 Paint.setColorFilter(ColorFilter filter) 方法。 ColorFilter 并不直接使用,而是使用它的子类。它共有三个子类:LightingColorFilter PorterDuffColorFilter 和 ColorMatrixColorFilter。
LightingColorFilter模拟简单的光照效果
LightingColorFilter 的构造方法是 LightingColorFilter(int mul, int add) ,参数里的 mul 和 add 都是和颜色值格式相同的 int 值,其中 mul 用来和目标像素相乘,add 用来和目标像素相加
一个「保持原样」的「基本 LightingColorFilter 」,mul 为 0xffffff,add 为 0x000000(也就是0),那么对于一个像素,它的计算过程就是
基于这个「基本 LightingColorFilter 」,你就可以修改一下做出其他的 filter。比如,如果你想去掉原像素中的红色,可以把它的 mul 改为 0x00ffff (红色部分为 0 ) ,那么它的计算过程就是
PorterDuffColorFilter
这个 PorterDuffColorFilter 的作用是使用一个指定的颜色和一种指定的 PorterDuff.Mode 来与绘制对象进行合成。它的构造方法是 PorterDuffColorFilter(int color, PorterDuff.Mode mode) 其中的 color 参数是指定的颜色, mode 参数是指定的 Mode。同样也是 PorterDuff.Mode ,不过和 ComposeShader 不同的是,PorterDuffColorFilter 作为一个 ColorFilter,只能指定一种颜色作为源,而不是一个 Bitmap
ColorMatrixColorFilter
ColorMatrixColorFilter 使用一个 ColorMatrix 来对颜色进行处理。 ColorMatrix 这个类,内部是一个 4x5 的矩阵
通过计算, ColorMatrix 可以把要绘制的像素进行转换。对于颜色 [R, G, B, A] ,转换算法是这样的
ColorMatrix 有一些自带的方法可以做简单的转换,例如可以使用 setSaturation(float sat) 来设置饱和度;另外你也可以自己去设置它的每一个元素来对转换效果做精细调整。如果你有需求,可以试一下这个库:StyleImageView
setXfermode(Xfermode xfermode)
"Xfermode" 其实就是 "Transfer mode",用 "X" 来代替 "Trans" 是一些美国人喜欢用的简写方式。严谨地讲, Xfermode 指的是你要绘制的内容和 Canvas 的目标位置的内容应该怎样结合计算出最终的颜色。但通俗地说,其实就是要你以绘制的内容作为源图像,以 View 中已有的内容作为目标图像,选取一个 PorterDuff.Mode 作为绘制内容的颜色处理方案。就像这样:
又是 PorterDuff.Mode 。 PorterDuff.Mode 在 Paint 一共有三处 API ,它们的工作原理都一样,只是用途不同:
另外,从上面的示例代码可以看出,创建 Xfermode 的时候其实是创建的它的子类 PorterDuffXfermode。而事实上,Xfermode 也只有这一个子类。所以在设置 Xfermode 的时候不用多想,直接用 PorterDuffXfermode 吧。
效果
效果类的 API ,指的就是抗锯齿、填充/轮廓、线条宽度等等这些。
setAntiAlias (boolean aa) 设置抗锯齿
setStyle(Paint.Style style)设置图形是线条风格还是填充风格的
线条形状
设置线条形状的一共有 4 个方法:setStrokeWidth(float width), setStrokeCap(Paint.Cap cap), setStrokeJoin(Paint.Join join), setStrokeMiter(float miter) 。
setStrokeWidth(float width)设置线条宽度。单位为像素,默认值是 0。
setStrokeCap(Paint.Cap cap)
设置线头的形状。线头形状有三种:BUTT 平头、ROUND 圆头、SQUARE 方头。默认为 BUTT。
setStrokeJoin(Paint.Join join)
设置拐角的形状。有三个值可以选择:MITER 尖角、 BEVEL 平角和 ROUND 圆角。默认为 MITER。
setStrokeMiter(float miter)
对于 setStrokeJoin() 的一个补充,它用于设置 MITER 型拐角的延长线的最大值。所谓「延长线的最大值」
当线条拐角为 MITER 时,拐角处的外缘需要使用延长线来补偿
色彩优化
Paint 的色彩优化有两个方法: setDither(boolean dither) 和 setFilterBitmap(boolean filter) 。它们的作用都是让画面颜色变得更加「顺眼」,但原理和使用场景是不同的。
setDither(boolean dither)设置图像的抖动。
抖动的原理和这个类似。所谓抖动(注意,它就叫抖动,不是防抖动,也不是去抖动,有些人在翻译的时候自作主张地加了一个「防」字或者「去」字,这是不对的),是指把图像从较高色彩深度(即可用的颜色数)向较低色彩深度的区域绘制时,在图像中有意地插入噪点,通过有规律地扰乱图像来让图像对于肉眼更加真实的做法。
比如向 1 位色彩深度的区域中绘制灰色,由于 1 位深度只包含黑和白两种颜色,在默认情况下,即不加抖动的时候,只能选择向上或向下选择最接近灰色的白色或黑色来绘制,那么显示出来也只能是一片白或者一片黑。而加了抖动后,就可以绘制出让肉眼识别为灰色的效果了:
像上面这样,用黑白相间的方式来绘制,就可以骗过肉眼,让肉眼辨别为灰色了
只要加这么一行代码,之后的绘制就是加抖动的了,现在不实用了,因为现在的 Android 版本的绘制,默认的色彩深度已经是 32 位的 ARGB_8888 ,效果已经足够清晰了。只有当你向自建的 Bitmap 中绘制,并且选择 16 位色的 ARGB_4444 或者 RGB_565 的时候,开启它才会有比较明显的效果。
setFilterBitmap(boolean filter)
设置是否使用双线性过滤来绘制 Bitmap
图像在放大绘制的时候,默认使用的是最近邻插值过滤,这种算法简单,但会出现马赛克现象;而如果开启了双线性过滤,就可以让结果图像显得更加平滑。
setPathEffect(PathEffect effect)
使用 PathEffect 来给图形的轮廓设置效果。对 Canvas 所有的图形绘制有效,也就是 drawLine() drawCircle() drawPath() 这些方法
具体说一下 Android 中的 6 种 PathEffect。PathEffect 分为两类,单一效果的 CornerPathEffect DiscretePathEffect DashPathEffect PathDashPathEffect ,和组合效果的 SumPathEffect ComposePathEffect。
CornerPathEffect
把所有拐角变成圆角。
DiscretePathEffect
把线条进行随机的偏离,让轮廓变得乱七八糟。乱七八糟的方式和程度由参数决定。
DashPathEffect
使用虚线来绘制线条。
PathDashPathEffect
这个方法比 DashPathEffect 多一个前缀 Path ,所以顾名思义,它是使用一个 Path 来绘制「虚线」
SumPathEffect
这是一个组合效果类的 PathEffect 。它的行为特别简单,就是分别按照两种 PathEffect 分别对目标进行绘制。
ComposePathEffect
这也是一个组合效果类的 PathEffect 。不过它是先对目标 Path 使用一个 PathEffect,然后再对这个改变后的 Path 使用另一个 PathEffect
上面这些就是 Paint 中的 6 种 PathEffect。它们有的是有独立效果的,有的是用来组合不同的 PathEffect 的,功能各不一样。
注意: PathEffect 在有些情况下不支持硬件加速,需要关闭硬件加速才能正常使用:
Canvas.drawLine()
和Canvas.drawLines()
方法画直线时,setPathEffect()
是不支持硬件加速的;PathDashPathEffect
对硬件加速的支持也有问题,所以当使用PathDashPathEffect
的时候,最好也把硬件加速关了。
剩下的两个效果类方法:setShadowLayer() 和 setMaskFilter() ,它们和前面的效果类方法有点不一样:它们设置的是「附加效果」,也就是基于在绘制内容的额外效果。
setShadowLayer(float radius, float dx, float dy, int shadowColor)
在之后的绘制内容下面加一层阴影。
效果就是上面这样。方法的参数里, radius 是阴影的模糊范围; dx dy 是阴影的偏移量; shadowColor 是阴影的颜色。
如果要清除阴影层,使用 clearShadowLayer() 。
注意:
- 在硬件加速开启的情况下, setShadowLayer() 只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。
- 如果 shadowColor 是半透明的,阴影的透明度就使用 shadowColor 自己的透明度;而如果 shadowColor 是不透明的,阴影的透明度就使用 paint 的透明度。
setMaskFilter(MaskFilter maskfilter)
为之后的绘制设置 MaskFilter。上一个方法 setShadowLayer() 是设置的在绘制层下方的附加效果;而这个 MaskFilter 和它相反,设置的是在绘制层上方的附加效果。
到现在已经有两个 setXxxFilter(filter) 了。前面有一个 setColorFilter(filter) ,是对每个像素的颜色进行过滤;而这里的 setMaskFilter(filter) 则是基于整个画面来进行过滤。
MaskFilter 有两种: BlurMaskFilter 和 EmbossMaskFilter。
BlurMaskFilter
模糊效果的 MaskFilter。
它的构造方法 BlurMaskFilter(float radius, BlurMaskFilter.Blur style) 中, radius 参数是模糊的范围, style 是模糊的类型。一共有四种:
NORMAL
: 内外都模糊绘制SOLID
: 内部正常绘制,外部模糊INNER
: 内部模糊,外部不绘制OUTER
: 内部不绘制,外部模糊(什么鬼?)
EmbossMaskFilter
浮雕效果的 MaskFilter。
获取绘制的 Path
这是效果类的最后一组方法,也是效果类唯一的一组 get 方法。
这组方法做的事是,根据 paint 的设置,计算出绘制 Path 或文字时的实际 Path。
这里你可能会冒出两个问题:
- 什么叫「实际 Path」? Path 就是 Path,这加上个「实际」是什么意思?
- 文字的 Path ?文字还有 Path?
这两个问题(咦好像有四个问号)的答案就在后面的内容里。
getFillPath(Path src, Path dst)
首先解答第一个问题:「实际 Path」。所谓实际 Path ,指的就是 drawPath() 的绘制内容的轮廓,要算上线条宽度和设置的 PathEffect。
默认情况下(线条宽度为 0、没有 PathEffect),原 Path 和实际 Path 是一样的;而在线条宽度不为 0 (并且模式为 STROKE 模式或 FLL_AND_STROKE ),或者设置了 PathEffect 的时候,实际 Path 就和原 Path 不一样了:
通过 getFillPath(src, dst) 方法就能获取这个实际 Path。方法的参数里,src 是原 Path ,而 dst 就是实际 Path 的保存位置。 getFillPath(src, dst) 会计算出实际 Path,然后把结果保存在 dst 里。
getTextPath(String text, int start, int end, float x, float y, Path path) / getTextPath(char[] text, int index, int count, float x, float y, Path path)
这里就回答第二个问题:「文字的 Path」。文字的绘制,虽然是使用 Canvas.drawText() 方法,但其实在下层,文字信息全是被转化成图形,对图形进行绘制的。 getTextPath() 方法,获取的就是目标文字所对应的 Path 。这个就是所谓「文字的 Path」。
这两个方法, getFillPath() 和 getTextPath() ,就是获取绘制的 Path 的方法。之所以把它们归类到「效果」类方法,是因为它们主要是用于图形和文字的装饰效果的位置计算,比如自定义下划线效果
到此为止, Paint 的第二类方法——效果类,就也介绍完了
初始化类
这一类方法很简单,它们是用来初始化 Paint 对象,或者是批量设置 Paint 的多个属性的方法。
4.1 reset()
重置 Paint 的所有属性为默认值。相当于重新 new 一个,不过性能当然高一些啦。
4.2 set(Paint src)
把 src 的所有属性全部复制过来。相当于调用 src 所有的 get 方法,然后调用这个 Paint 的对应的 set 方法来设置它们。
4.3 setFlags(int flags)
批量设置 flags。相当于依次调用它们的 set 方法
这行代码,和下面这两行是等价的:
setFlags(flags) 对应的 get 方法是 int getFlags()。