Paint基础练习

参考:https://hencoder.com/ui-1-2/
练习github地址:https://github.com/fandazeng/ViewDemo

Paint 的 API 大致分为4类:

  • 颜色
  • 效果
  • drawText()相关
  • 初始化

一.颜色

Canvas 绘制的内容,有三层对颜色的处理:

基本颜色

像素的基本颜色,根据绘制内容的不同而有不同的控制方式: Canvas 的颜色填充类方法 drawColor/RGB/ARGB() 的颜色,是直接写在方法的参数里,通过参数来设置的; drawBitmap() 的颜色,是直接由 Bitmap 对象来提供的;除此之外,是图形和文字的绘制,它们的颜色就需要使用 paint 参数来额外设置了。

Paint 设置颜色的方法有两种:一种是直接用 Paint.setColor/ARGB() 来设置颜色,另一种是使用 Shader 来指定着色方案。上一篇已经用过第一种了,这里只说第二种。

setShader(Shader shader) 设置 Shader

Shader 是着色器的意思,设置的是一个颜色方案,或者说是一套着色规则。当设置了 Shader 之后,Paint 在绘制图形和文字时就不使用 setColor/ARGB() 设置的颜色了,而是使用 Shader 的方案中的颜色。

Shader 是一个超类,我们主要用的是它的子类,共有5个,分别是 LinearGradient 、 RadialGradient 、 SweepGradient 、 BitmapShader 、 ComposeShader 。

LinearGradient 线性渐变

设置两个点和两种颜色,以这两个点作为端点,使用两种颜色的渐变来绘制颜色

构造方法:
LinearGradient(float x0, float y0, float x1, float y1,
int color0, int color1, Shader.TileMode tile)

参数:
x0 y0 x1 y1:渐变的两个端点的位置
color0 color1 是端点的颜色
tile:端点范围之外的着色规则,类型是 TileMode。TileMode 一共有 3 个值可选: CLAMP(默认),
MIRROR 和 REPEAT。CLAMP 会在端点之外延续端点处的颜色;MIRROR 是镜像模式;REPEAT 是重复模式

示例代码:
mPaint.setShader(new LinearGradient(100, 100, 500, 500, Color.parseColor("#E91E63"),
            Color.parseColor("#2196F3"), Shader.TileMode.CLAMP));

canvas.drawCircle(300, 300, 200, mPaint);

RadialGradient 辐射渐变

从中心向周围辐射状的渐变

构造方法:
RadialGradient(float centerX, float centerY, float radius,
int centerColor, int edgeColor, TileMode tileMode)

参数:
centerX centerY:辐射中心的坐标
radius:辐射半径
centerColor:辐射中心的颜色
edgeColor:辐射边缘的颜色
tileMode:辐射范围之外的着色模式

示例代码:
mPaint.setShader(new RadialGradient(300, 300, 200,
            Color.parseColor("#E91E63"), Color.parseColor("#2196F3"), Shader.TileMode.CLAMP));

canvas.drawCircle(300, 300, 200, mPaint);

SweepGradient 扫描渐变

构造方法:
SweepGradient(float cx, float cy, int color0, int color1)

参数:
cx cy :扫描的中心
color0:扫描的起始颜色
color1:扫描的终止颜色

示例代码:
mPaint.setShader(new SweepGradient(300, 300,
            Color.parseColor("#E91E63"), Color.parseColor("#2196F3")));

canvas.drawCircle(300, 300, 200, mPaint);

BitmapShader 图片着色器

用 Bitmap 的像素来作为图形或文字的填充

构造方法:
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)

参数:
bitmap:用来做模板的 Bitmap 对象
tileX:横向的 TileMode
tileY:纵向的 TileMode

示例代码:
mPaint.setShader(new BitmapShader(BitmapFactory.decodeResource(getResources(), 
R.drawable.batman), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));

canvas.drawCircle(300, 300, 200, mPaint);

如果你想绘制圆形的 Bitmap,就别用 drawBitmap() 了,改用 drawCircle() + BitmapShader
就可以了(其他形状同理)

ComposeShader 混合着色器

所谓混合,就是把两个 Shader 一起使用

构造方法:
ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)

参数:
shaderA, shaderB:两个相继使用的 Shader
mode: 两个 Shader 的叠加模式,即 shaderA 和 shaderB 应该怎样共同绘制。
它的类型是 PorterDuff.Mode

示例代码:
Shader batman = new BitmapShader(BitmapFactory.decodeResource(getResources(), R.drawable.batman), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
Shader logo = new BitmapShader(BitmapFactory.decodeResource(getResources(), R.drawable.batman_logo), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

mPaint.setShader(new ComposeShader(batman,logo, PorterDuff.Mode.DST_IN));

canvas.drawCircle(300, 300, 300, mPaint);

注意:上面这段代码中我使用了两个 BitmapShader 来作为 ComposeShader() 的参数,
而 ComposeShader() 在硬件加速下是不支持两个相同类型的 Shader 的,所以这里也
需要关闭硬件加速才能看到效果。

//开启软件加速
setLayerType(LAYER_TYPE_SOFTWARE,mPaint);

PorterDuff.Mode

用来指定两个图像共同绘制时的颜色策略,它是一个 enum,不同的 Mode 可以指定不同的策略。
「颜色策略」的意思,就是说把源图像绘制到目标图像处时应该怎样确定二者结合后的颜色,而对于
ComposeShader(shaderA, shaderB, mode) 这个具体的方法,就是指应该怎样把 shaderB
绘制在 shaderA 上来得到一个结合后的 Shader。

具体来说, PorterDuff.Mode 一共有 17 个,可以分为两类:

  1. Alpha 合成 (Alpha Compositing)

  2. 混合 (Blending)

效果如下:

源图像和目标图像:

Alpha 合成:

混合,也就是 Photoshop 等制图软件里都有的那些混合模式:

setColorFilter(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 用来和目标像素相加:

R' = R * mul.R / 0xff + add.R  
G' = G * mul.G / 0xff + add.G  
B' = B * mul.B / 0xff + add.B 

一个「保持原样」的「基本 LightingColorFilter 」,mul 为 0xffffff,add 为 0x000000(也就是0),
那么对于一个像素,它的计算过程就是:

R' = R * 0xff / 0xff + 0x0 = R // R' = R  
G' = G * 0xff / 0xff + 0x0 = G // G' = G  
B' = B * 0xff / 0xff + 0x0 = B // B' = B  

示例代码:

ColorFilter colorFilter1 = new LightingColorFilter(0x00ffff, 0x000000);
mPaint.setColorFilter(colorFilter1);

Bitmap batman = BitmapFactory.decodeResource(getResources(), R.drawable.batman);
canvas.drawBitmap(batman, 0, 0, mPaint);

PorterDuffColorFilter

使用一个指定的颜色和一种指定的 PorterDuff.Mode 来与绘制对象进行合成。

构造方法:

PorterDuffColorFilter(int color, PorterDuff.Mode mode)

参数:
color:指定的颜色
mode :指定的 Mode

示例代码:

Bitmap batman = BitmapFactory.decodeResource(getResources(), R.drawable.batman);
ColorFilter porterFilter = new PorterDuffColorFilter(Color.parseColor("#80000000"), PorterDuff.Mode.SRC_OVER);
mPaint.setColorFilter(porterFilter);
canvas.drawBitmap(batman, 0, 0, mPaint);

ColorMatrixColorFilter

使用一个 ColorMatrix 来对颜色进行处理。 ColorMatrix 这个类,内部是一个 4x5 的矩阵:

[ a, b, c, d, e,

  f, g, h, i, j,

  k, l, m, n, o,

  p, q, r, s, t ]

通过计算, ColorMatrix 可以把要绘制的像素进行转换。对于颜色 [R, G, B, A] ,转换算法是这样的:

R’ = a*R + b*G + c*B + d*A + e;  
G’ = f*R + g*G + h*B + i*A + j;  
B’ = k*R + l*G + m*B + n*A + o;  
A’ = p*R + q*G + r*B + s*A + t;  

ColorMatrix 有一些自带的方法可以做简单的转换,例如可以使用 setSaturation(float sat)
来设置饱和度;另外你也可以自己去设置它的每一个元素来对转换效果做精细调整。

示例代码:

ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(10);
ColorFilter colorFilter = new ColorMatrixColorFilter(colorMatrix);
mPaint.setColorFilter(colorFilter);

Bitmap batman = BitmapFactory.decodeResource(getResources(), R.drawable.batman);
canvas.drawBitmap(batman, 0, 0, mPaint);

setXfermode(Xfermode xfermode)

Xfermode 指的是你要绘制的内容和 Canvas 的目标位置的内容应该怎样结合计算出最终的颜色。
但通俗地说,其实就是要你以绘制的内容作为源图像,以 View 中已有的内容作为目标图像,
选取一个 PorterDuff.Mode 作为绘制内容的颜色处理方案。

创建 Xfermode 的时候其实是创建的它的子类 PorterDuffXfermode。
而事实上,Xfermode 也只有这一个子类。

PorterDuff.Mode 在 Paint 一共有三处 API ,它们的工作原理都一样,只是用途不同:

示例代码:

Bitmap batman = BitmapFactory.decodeResource(getResources(), R.drawable.batman);
Bitmap logo = BitmapFactory.decodeResource(getResources(), R.drawable.batman_logo);

canvas.drawBitmap(batman, 0, 0, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(logo, 0, 0, mPaint);
mPaint.setXfermode(null);

注意:要使用离屏缓冲,不然没有效果,有两种方式实现

Canvas.saveLayer()

可以做短时的离屏缓冲。使用方法很简单,在绘制代码的前后各加一行代码,
在绘制之前保存,绘制之后恢复。这种方法性能更高。

int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
//代码省略
canvas.restoreToCount(saved);

View.setLayerType()

View.setLayerType() 是直接把整个 View 都绘制在离屏缓冲中。
setLayerType(LAYER_TYPE_HARDWARE) 是使用 GPU 来缓冲, setLayerType(LAYER_TYPE_SOFTWARE)
是直接直接用一个 Bitmap 来缓冲。

setLayerType(LAYER_TYPE_SOFTWARE,mPaint);

到此为止,前面讲的就是 Paint 的第一类 API——关于颜色的三层设置:直接设置颜色的 API 用来给图形和
文字设置颜色; setColorFilter() 用来基于颜色进行过滤处理; setXfermode() 用来处理源图像和
View 已有内容的关系。

二.效果

线条形状

设置线条形状的一共有 4 个方法:setStrokeWidth(float width), setStrokeCap(Paint.Cap cap),
setStrokeJoin(Paint.Join join), setStrokeMiter(float miter)。

1.setStrokeWidth(float width)

设置线条宽度。单位为像素,默认值是 0

示例代码:

mPaint.setStrokeWidth(20);
mPaint.setStrokeWidth(100);

2.setStrokeCap(Paint.Cap cap)

设置线头的形状。线头形状有三种:BUTT 平头、ROUND 圆头、SQUARE 方头。默认为 BUTT

示例代码:

mPaint.setStrokeCap(Paint.Cap.BUTT);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeCap(Paint.Cap.SQUARE);

3.setStrokeJoin(Paint.Join join)

设置拐角的形状。有三个值可以选择:MITER 尖角、 BEVEL 平角和 ROUND 圆角。默认为 MITER

示例代码:

canvas.save();

canvas.translate(100, 100);
mPaint.setStrokeJoin(Paint.Join.MITER);
canvas.drawPath(mPath, mPaint);

canvas.translate(300, 0);
mPaint.setStrokeJoin(Paint.Join.ROUND);
canvas.drawPath(mPath, mPaint);

canvas.translate(300, 0);
mPaint.setStrokeJoin(Paint.Join.BEVEL);
canvas.drawPath(mPath, mPaint);

canvas.restore();

色彩优化

Paint 的色彩优化有两个方法: setDither(boolean dither) 和 setFilterBitmap(boolean filter) 。
它们的作用都是让画面颜色变得更加「顺眼」,但原理和使用场景是不同的。

1.setDither(boolean dither)

设置图像的抖动,现在不太实用

示例代码:

paint.setDither(true);

2.setFilterBitmap(boolean filter)

设置是否使用双线性过滤来绘制 Bitmap,图像在放大绘制的时候,默认使用的是最近邻插值过滤,
这种算法简单,但会出现马赛克现象;而如果开启了双线性过滤,就可以让结果图像显得更加平滑。

示例代码:

paint.setFilterBitmap(true);

以上就是 Paint 的两个色彩优化的方法: setDither(dither) ,设置抖动来优化色彩深度降低时的绘制效果;
setFilterBitmap(filterBitmap) ,设置双线性过滤来优化 Bitmap 放大绘制的效果。

setPathEffect(PathEffect effect)

使用 PathEffect 来给图形的轮廓设置效果。对 Canvas 所有的图形绘制有效,也就是
drawLine() drawCircle() drawPath() 这些方法。

共有6种 PathEffect 。PathEffect 分为两类,单一效果的 CornerPathEffect 、DiscretePathEffect 、
DashPathEffect 、PathDashPathEffect ,和组合效果的 SumPathEffect 、ComposePathEffect。

1.CornerPathEffect

把所有拐角变成圆角。

它的构造方法 CornerPathEffect(float radius) 的参数 radius 是圆角的半径

示例代码:

PathEffect cornerPathEffect = new CornerPathEffect(20);
mPaint.setPathEffect(cornerPathEffect);
canvas.drawPath(mPath, mPaint);

2.DiscretePathEffect

把线条进行随机的偏离,让轮廓变得乱七八糟。

DiscretePathEffect 具体的做法是,把绘制改为使用定长的线段来拼接,并且在拼接的时候
对路径进行随机偏离。它的构造方法 DiscretePathEffect(float segmentLength, float deviation)
的两个参数中,segmentLength 是用来拼接的每个线段的长度,deviation 是偏离量。

示例代码:

PathEffect discretePathEffect = new DiscretePathEffect(20, 5);
mPaint.setPathEffect(discretePathEffect);
canvas.drawPath(mPath, mPaint);

3.DashPathEffect

使用虚线来绘制线条。

它的构造方法 DashPathEffect(float[] intervals, float phase) 中, 第一个参数 intervals
是一个数组,它指定了虚线的格式:数组中元素必须为偶数(最少是 2 个),按照「画线长度、空白长度、
画线长度、空白长度」……的顺序排列,例如上面代码中的 20, 5, 10, 5 就表示虚线是按照「画 20 像素、
空 5 像素、画 10 像素、空 5 像素」的模式来绘制;第二个参数 phase 是虚线的偏移量。

示例代码:

PathEffect dashPathEffect = new DashPathEffect(new float[]{20, 10, 5, 10}, 0);
mPaint.setPathEffect(dashPathEffect);
canvas.drawPath(mPath, mPaint);

4.PathDashPathEffect

使用一个 Path 来绘制「虚线」。

它的构造方法 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:变体

示例代码:

//三角形
Path dashPath = new Path();
dashPath.lineTo(20, -30);
dashPath.lineTo(40, 0);
dashPath.close();
mPathDashPathEffect = new PathDashPathEffect(dashPath, 50, 0, PathDashPathEffect.Style.MORPH);
mPaint.setPathEffect(mPathDashPathEffect);
canvas.drawPath(mPath, mPaint);

5.SumPathEffect

这是一个组合效果类的 PathEffect 。它的行为特别简单,就是分别按照两种 PathEffect 分别对目标进行绘制。即有两条线。

构造方法传入两个 PathEffect 对象。

示例代码:

PathEffect sumPathEffect = new SumPathEffect(dashPathEffect, discretePathEffect);
mPaint.setPathEffect(sumPathEffect);
canvas.drawPath(mPath, mPaint);

6.ComposePathEffect

这也是一个组合效果类的 PathEffect 。不过它是先对目标 Path 使用一个 PathEffect,然后再对这个改变后
的 Path 使用另一个 PathEffect。即只有一条线。

构造方法传入两个 PathEffect 对象。 innerpe 是先应用的, outerpe 是后应用的

示例代码:

PathEffect composePathEffect = new ComposePathEffect(dashPathEffect, discretePathEffect);
mPaint.setPathEffect(composePathEffect);
canvas.drawPath(mPath, mPaint);

注意: PathEffect 在有些情况下不支持硬件加速,需要关闭硬件加速才能正常使用:

  1. Canvas.drawLine() 和 Canvas.drawLines() 方法画直线时,setPathEffect() 是不支持硬件加速的;

  2. PathDashPathEffect 对硬件加速的支持也有问题,所以当使用 PathDashPathEffect 的时候,最好也把硬件加速关了。

setShadowLayer(float radius, float dx, float dy, int shadowColor)

在之后的绘制内容 下面 加一层阴影。

示例代码:

mPaint.setShadowLayer(4, 10, 10, Color.BLUE);
//清除阴影层
//mPaint.clearShadowLayer();
canvas.drawText("Hello Fanda!", 200, 200, mPaint);

方法的参数里, radius 是阴影的模糊范围; dx dy 是阴影的偏移量; shadowColor 是阴影的颜色。

注意:

  • 在硬件加速开启的情况下, setShadowLayer() 只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。

  • 如果 shadowColor 是半透明的,阴影的透明度就使用 shadowColor 自己的透明度;而如果 shadowColor 是不透明的,阴影的透明度就使用 paint 的透明度。

setMaskFilter(MaskFilter maskfilter)

为之后的绘制设置 MaskFilter。上一个方法 setShadowLayer() 是设置的在绘制层下方的附加效果;而这个 MaskFilter 和它相反,设置的是在绘制层上方的附加效果。

到现在已经有两个 setXxxFilter(filter) 了。前面有一个 setColorFilter(filter) ,
是对每个像素的颜色进行过滤;而这里的 setMaskFilter(filter) 则是基于整个画面来进行过滤。

MaskFilter 有两种: BlurMaskFilter(模糊效果)和 EmbossMaskFilter(浮雕效果)。这里只说模糊效果。

它的构造方法 BlurMaskFilter(float radius, BlurMaskFilter.Blur style) 中, radius 参数是模糊的范围,style 是模糊的类型。一共有四种:

  • NORMAL: 内外都模糊绘制(默认)
  • SOLID: 内部正常绘制,外部模糊
  • INNER: 内部模糊,外部不绘制
  • OUTER: 内部不绘制,外部模糊
示例代码:
MaskFilter maskFilter1 = new BlurMaskFilter(50, BlurMaskFilter.Blur.NORMAL);
mPaint.setMaskFilter(maskFilter1);
canvas.drawBitmap(bitmap, 100, 50, mPaint);

初始化类

这一类方法很简单,它们是用来初始化 Paint 对象,或者是批量设置 Paint 的多个属性的方法。

reset()

重置 Paint 的所有属性为默认值。相当于重新 new 一个,不过性能当然高一些。

set(Paint src)

把 src 的所有属性全部复制过来。相当于调用 src 所有的 get 方法,然后调用这个 Paint 的对应的 set 方法来设置它们。

setFlags(int flags)

批量设置 flags。相当于依次调用它们的 set 方法。

paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);

这行代码,和下面这两行是等价的:

paint.setAntiAlias(true);  
paint.setDither(true); 

颜色类、效果类、文字绘制相关以及初始化类。其中颜色类、效果类和初始化类都已经在这节里面讲过了,剩下的一类——文字绘制类,下一节单独讲。

练习演示: