自定义View2 Paint
自定义View1-2 Paint详解
1颜色
Canvas绘制的内容,有三层对颜色的处理:
- 基本颜色:1、Canvas.drawColor/ARGB() –颜色参数 2、Canvas.drawBitmap()–bitmap参数 3、Canvas图形和文字绘制–paint参数。
- ColorFilter:Paiint.setColorFilter(CorlorFilter)。
- Xfermode:Paint.setXfermode(Xfermode)。
1.1 基本颜色
像素的基本颜色,根据绘制内容的不同而有不同的控制方式:
Canvas的颜色填充类方法drawColor/RBG/ARGB() 直接作为参数传入。
drawBitmap()的颜色与bitmap参数的像素颜色相同。
图形和文字(drawCircle()/drawPath()/drawText()…) 在paint参数中设置。
Paint设置颜色的方法有两种:一种是直接用 Paint.setColor/ARGB() 来设置颜色,另一种是使用 shader 来指定着色方案。
1.1.1 直接设置颜色
1.1.1.1 setColor(int color)
paint.color = Color.parseColor("#009688") |
1.1.1.2 setARGB(int a, int r, int g, int b)
和setColor(color)一样。
paint.setARGB(100, 255, 0, 0) |
1.1.2setShader(Shader shader) 设置 Shader
除了直接设置颜色,Paint还可以使用Shader。
Shader的中文叫做着色器,它设置的是一个着色方案,当设置了Shader之后,Paint在绘制图形和文字时就不使用setColor/ARGB()设置的颜色了,而是使用Shader的方案中的颜色。
在Android的绘制里使用的Shader,并不是直接用Shader这个类,而是用它的几个子类。具体来讲有LinearGradient、RadialGradient、SweepGradient、BitmapShae、ComposeShader这几个:
1.1.2.1 LinearGradient 线性渐变
设置两个点和两种颜色,以这两个点作为端点,使用这两种颜色的渐变来绘制颜色。
val shder = LinerGradient(100f, 100f, 500f, 500f, Color.parseColor("#E91E63"), |
其他形状以及文字都可以这样设置颜色
注意:在设置了Shader的情况下,Paint.setColor/ARGB()所设置的颜色就不再起作用
构造方法:
LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile) |
参数:
x0, y0, x1, y1:渐变的两个端点的位置 |
1.1.2.2 RadialGradient辐射渐变
辐射渐变很好理解,就是从中心像周围辐射状的渐变。
val shader = RadialGradient(300f, 300f, 200f, Color.parseColor("#E91E63"), Color.parseColor("#2196F3"), Shaer.TIleMode.CLAMP) |
构造方法:
RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, TileMode tileMode) |
参数:
centerX, centerY: 辐射中心的坐标 |
1.1.2.3 SweepGradient 扫描渐变
val shader = SweepGradient(300f, 300f, Color.parseColor("#E91E63"), |
构造方法:
SweepGradient(float cx, float cy, int color, int color) |
参数:
cx, cy:扫描的中心 |
1.1.2.4 BitmapShader
用Bitmap来着色,也就是用Bitmap的像素来作为图形或文字的填充
// 加载原始的Bitmap |
构造方法:
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY) |
参数:
bitmap: 用来作模板的Bitmap对象 |
1.1.2.5 ComposeShader 混合着色器
所谓混合,就是把两个Shader一起使用
val originalBitmap1 = BitmapFactory.decodeResource(resource, R.drawable.batman) |
构造方法:
ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) |
参数:
shaderA, shaderB: 两个相继使用的Shader |
PorterDuff.Mode是用来指定两个图像共同绘制时的颜色策略的。PoterDuff.Mode一共有17个,可以分为两类:
Alpha合成(Alpha Compositing)
混合(Blending)
1.2 setColorFilter(ColorFilter colorFilter)
ColorFilter 这个类,它的名字已经足够解释它的作用:为绘制设置颜色过滤。颜色过滤的意思,就是为绘制的内容设置一个统一的过滤策略,然后Canvas.drawXXX()方法会对每个像素都进行过滤后再绘制出来。
1.2.1 LightingColorFilter
这个LightingColorFilter是用来模拟简单的光照效果的
LightingColorFilter的构造方法是LightingColorFilter(int mul, int add),参数里的mul和add都是和颜色格式相同的int值,其中mul用来和目标像素相乘,add用来和目标像素相加:
R' = R * mul.R / 0xff + add.R |
若是要使用LightingColorFilter并保持原样,则将mul设置为0xfffff, add设置为0x000000
R' = R * 0xff / 0xff + 0x0 = R // R' = R |
基于这个基本,就可以修改一下做出其它的filter。比如,如果你想去掉原像素中的红色,可以把它的mul改为0xffff,那么它的计算过程就是:
R' = R * 0x0 / 0xff + 0x0 = 0 // 红色被移除 |
具体效果是这样的:
val lightingColorFilter = LightingColorFilter(0x00ffffff, 0x000000) |
1.2.2 PorterDuffColorFilter
这个PorterDuffColorFilter的作用是使用一个指定的颜色和一种指定的PorterDuff.Mode来与绘制对象进行合成。它的构造方法是PorterDuffCOlorFilter(int color, PorterDuff.Mode mode)
1.2.3 ColorMatrixColorFilter
ColorMatrixColorFilter使用一个ColorMatrix对颜色进行处理。ColorMatrix这个类,内部是一个4*5的矩阵:
[ a, b, c, d, e, |
通过计算,ColorMatiex可以把想要绘制的像素进行转换。对于颜色[R,G,B,A],转换算法是这样的:
R’ = a*R + b*G + c*B + d*A + e; |
ColorMatrix有一些自带的方法可以做简单的转换,例如可以使用setSaturaion(float sat)来设置饱和度
val bitmap = BitmapFactory.decodeResource(resources, R.drawale.batman) |
1.3 setXfermode(Xfermode xfermode)
Xfermode指的是你要绘制的内容和Canvas的目标位置的内容应该怎样结合计算出最终的颜色。但通俗地说,其实就是要以你绘制的内容作为源图像,以View中已有的内容作为目标图像,选取一个Poteruff.Mode作为绘制颜色处理的方案。
val xfermode = PoterDuffXfermode(PorterDuff.Mode.DST_IN) |
又是PorterDuff.Mode。PorterDuff.Mode在Paint一共有三处API,它们的工作原理都一样,只是用途不同:
- ComposeShader:混合两个Shader
- PorterDuffColorFilter:增加一个单色的ColorFilter
- Xfermode:设置绘制内容和View中已有内容的混合计算方式
注意事项
Xfermode的使用很简单,不过有两点需要注意:
1、使用离屏缓冲(Off-screen Buffer)
实质上,上面这段例子代码,如果直接执行的话是不会绘制出图中效果的
为什么:
照逻辑我们会认为,在第二步画圆的时候,跟它共同计算的是第一步绘制的方形。但实际上,却是整个 View的显示区域都在画圆的时候参与计算,并且 View自身的底色并不是默认的透明色,而且是遵循一种迷之逻辑,导致不仅绘制的是整个圆的范围,而且在范围之外都变成了黑色。
通过使用离屏缓冲,把要绘制的内容单独绘制在缓冲层, Xfermode的使用就不会出现奇怪的结果了。使用离屏缓冲有两种方式:
Canvas.saveLayer()
saveLayer()可以做短时的离屏缓冲。使用方法很简单,在绘制代码的前后各加一行代码,在绘制之前保存,绘制之后恢复:
val saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG) |
View.setLayerType()
View.setLayerType()是直接把整个 View都绘制在离屏缓冲中。 setLayerType(LAYER_TYPE_HARDWARE)是使用 GPU 来缓冲, setLayerType(LAYER_TYPE_SOFTWARE)是直接直接用一个Bitmap来缓冲。
2、控制好透明区域
使用 Xfermode 来绘制的内容,除了注意使用离屏缓冲,还应该注意控制它的透明区域不要太小,要让它足够覆盖到要和它结合绘制的内容,否则得到的结果很可能不是你想要的。
好,到此为止,前面讲的就是Paint的第一类 API——关于颜色的三层设置:直接设置颜色的 API 用来给图形和文字设置颜色; setColorFilter()用来基于颜色进行过滤处理;setXfermode()用来处理源图像和 View已有内容的关系。
效果
2.1 setAntiAlias (boolean aa) 设置抗锯齿
抗锯齿默认是关闭的,如果需要抗锯齿,需要显式地打开。
paint.isAntiAlias = true |
另外,除了setAntiAlias(aa)方法,打开抗锯齿还有一个更方便的方式:构造方法。创建 Paint对象的时候,构造方法的参数里加一个
ANTI_ALIAS_FLAG的flag,就可以在初始化的时候开启抗锯齿
val paint = Paint(Paint.ANTI_ALIAS_FLAG) |
2.2 setStyle (Paint.Sytle style)
setStyle(style)用来设置图形是线条风格还是填充风格的(也可以二者并用):
paint.style = Paint.Style.FILL // FILL 模式,填充 |
paint.style = Paint.Style.STROKE //STROKE 模式,画线 |
paint.style = Paint.Style.FILL_AND_STROKE // FILL_AND_STROKE 模式,填充 + 画线 |
FILL模式是默认模式,所以如果之前没有设置过其他的 Style,可以不用 setStyle(Paint.Style.FILL) 这句。
2.3 线条形状
设置线条形状一共有4个方法:setStrokeWidth(float width),setStrokeCap(Paint.Cap cap),setStrokeJoin(Paint.Join),setStrokeMiter(float miter)
2.3.1 线条形状
设置线条宽度。单位为像素,默认值是 0。
paint.style = Paint.Style.STROKE |
线条宽度 0 和 1 的区别
默认情况下,线条的宽度为0,但这个时候它依然能够画出线,线条的宽度为1像素。那么它和线条宽度为1有什么区别呢?
可以为Canvas设置Matrix来实现几何变换,2像素的线条在Canvas放大两倍后会被以4像素绘制,而当线条宽度被设置为0是,它的宽度就被固定为1像素,就算Canvas通过几何变换被放大,它依然会被以1像素宽度来绘制。
2.3.2 setStrokeCap(Paint.Cap cap)
设置线头的形状。线头形状有三种:BUTT平头、ROUND圆头、SQUARE方头。默认为BUTT。
paint.strokeCap = Paint.Cap.BUTT |
2.3.3 setStrokeJoin(Paint.Join join)
设置拐角的形状。有三个值可以选择:MITER尖角、BEVEL平角和ROUND圆角。默认为MITER
2.3.4 setStrokeMiter(float miter)
这个方法是对于 setStrokeJoin()的一个补充,它用于设置MITER型拐角的延长线的最大值。当延长线过长时,就会被转为BEVEL型。
色彩优化
Paint的色彩优化有两个昂发:setDither(boolean dither) 和 setFilterBitmap(boolean filter)
2.4.1 setDither(boolean dither)
paint.isDither = true |
2.4.2 setFilterBitmap(boolean filter)
设置是否使用双线性过滤来绘制 Bitmap
paint.isFilterBitmap = true |
setPathEffect(PathEffect effect)
使用PathEffect来给图形的轮廓设置效果。对Canvas所有的图形绘制有效,也就是drawLine(),drawCircle(),drawPath()这些方法。
Android 中的 6 种 PathEffect
。PathEffect
分为两类,单一效果的 CornerPathEffect
DiscretePathEffect
DashPathEffect
PathDashPathEffect
,和组合效果的 SumPathEffect
ComposePathEffect
。
2.5.1 CornerPathEffect
把所有拐角变成圆角
val pathEffect = CornerPathEffect(20f) |
它的构造方法 CornerPathEffect(float radius)
的参数 radius
是圆角的半径。
2.5.2 DiscretePathEffect
val pathEffect = DiscretePathEffect(20f, 5f) |
DiscretePathEffect
具体的做法是,把绘制改为使用定长的线段来拼接,并且在拼接的时候对路径进行随机偏离。它的构造方法 DiscretePathEffect(float segmentLength, float deviation)
的两个参数中, segmentLength
是用来拼接的每个线段的长度, deviation
是偏离量。这两个值设置得不一样,显示效果也会不一样。
2.5.3 DashPathEffect
使用虚线来绘制线条。
val pathEffect: PathEffect = DashPathEffect(floatArrayOf(20f, 10f, 5f, 10f), 0f) |
它的构造方法 DashPathEffect(float[] intervals, float phase)
中, 第一个参数 intervals
是一个数组,它指定了虚线的格式:数组中元素必须为偶数(最少是 2 个),按照「画线长度、空白长度、画线长度、空白长度」……的顺序排列,例如上面代码中的 20, 5, 10, 5
就表示虚线是按照「画 20 像素、空 5 像素、画 10 像素、空 5 像素」的模式来绘制;第二个参数 phase
是虚线的偏移量。
2.5.4 PathDashPathEffect
这个方法比 DashPathEffect
多一个前缀 Path
,所以顾名思义,它是使用一个 Path
来绘制「虚线」。具体看图吧:
val pathEffect: PathEffect = PathDashPathEffect(dashPath, 40f, 0f, PathDashPathEffect.Style.TRANSLATE) |
它的构造方法 PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style)
中, shape
参数是用来绘制的 Path
; advance
是两个相邻的 shape
段之间的间隔,不过注意,这个间隔是两个 shape
段的起点的间隔,而不是前一个的终点和后一个的起点的距离; phase
和 DashPathEffect
中一样,是虚线的偏移;最后一个参数 style
,是用来指定拐弯改变的时候 shape
的转换方式。style
的类型为 PathDashPathEffect.Style
,是一个 enum
,具体有三个值:
TRANSLATE
:位移ROTATE
:旋转MORPH
:变体
2.5.5 SumPathEffect
这是一个组合效果类的 PathEffect
。它的行为特别简单,就是分别按照两种 PathEffect
分别对目标进行绘制。
val dashEffect = DashPathEffect(floatArrayOf(20f, 10f), 0f) |
2.5.6 ComposePathEffect
val dashEffect = DashPathEffect(floatArrayOf(20f, 10f), 0f) |
2.6 setShadowLayer(float radius, float dx, float dy, int shadowColor)
在之后绘制的内容下面加一层阴影
val text = "hyq" |
radius 是阴影的模糊范围; dx dy 是阴影的偏移量; shadowColor是阴影的颜色。
如果要清除阴影层,使用 clearShadowLayer()
注意:
- 在硬件加速开启的情况下,
setShadowLayer()
只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。 - 如果 shadowColor是半透明的,阴影的透明度就使用 shadowColor自己的透明度;而如果 shadowColor是不透明的,阴影的透明度就使用 paint的透明度。
2.7 setMaskFilter(MaskFilter maskfilter)
为之后的绘制设置 MaskFilter。上一个方法 setShadowLayer()是设置的在绘制层下方的附加效果;而这个 MaskFilter 和它相反,设置的是在绘制层上方的附加效果。
到现在已经有两个 setXxxFilter(filter)了。前面有一个setColorFilter(filter),是对每个像素的颜色进行过滤;而这里的setMaskFilter(filter) 则是基于整个画面来进行过滤
MaskFilter有两种BlurMaskFilter和 EmbossMaskFilter。
2.7.1 BlurMaskFilter
模糊效果的 MaskFilter
。
val maskFilter = BlurMaskFilter(50f, BlurMaskFilter.Blur.NORMAL) |
它的构造方法 BlurMaskFilter(float radius, BlurMaskFilter.Blur style)
中, radius
参数是模糊的范围, style
是模糊的类型。一共有四种:
NORMAL
: 内外都模糊绘制SOLID
: 内部正常绘制,外部模糊INNER
: 内部模糊,外部不绘制OUTER
: 内部不绘制,外部模糊(什么鬼?)
2.7.2 EmbossMaskFilter
浮雕效果的 MaskFilter
。
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.batman) |
它的构造方法 EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius)
的参数里, direction
是一个 3 个元素的数组,指定了光源的方向; ambient
是环境光的强度,数值范围是 0 到 1; specular
是炫光的系数; blurRadius
是应用光线的范围。
2.8 获取绘制的 Path
这是效果类的最后一组方法,也是效果类唯一的一组 get
方法。
这组方法做的事是,根据 paint
的设置,计算出绘制 Path
或文字时的实际 Path。
2.8.1 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
里。
2.8.2 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
」
3 drawText() 相关
Paint
有些设置是文字绘制相关的,即和 drawText()
相关的。
4 初始化类
这一类方法很简单,它们是用来初始化 Paint
对象,或者是批量设置 Paint
的多个属性的方法。
4.1 reset()
重置 Paint
的所有属性为默认值。相当于重新 new
一个,不过性能当然高一些啦。
4.2 set(Paint src)
把 src
的所有属性全部复制过来。相当于调用 src
所有的 get
方法,然后调用这个 Paint
的对应的 set
方法来设置它们。
4.3 setFlags(int flags)
批量设置 flags。相当于依次调用它们的 set
方法。例如:
paint.flags = Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG |
这行代码,和下面这两行是等价的:
paint.setAntiAlias(true); |