好记性不如烂博客,古人诚不欺我。
看本篇文章前,请先阅读治花里胡哨(二)征服自定义View,各种最基本的几何图形的 draw() 方法你都会了吗?
作为 花里胡哨系列的第三篇
,这篇文章就详细的讲解一下 Paint
的各种 api
的使用。我们要知道 View
上的内容都是通过 Canvas
画出来的,但是画成什么样子,都是需要通过 Paint
来指挥的,所以说对于 Paint
了解的越详细,那么我们在后期绘制时,可用的方法就越多。废话不多说,马上进入主题。
一、 4 种最常见设置
一般情况下,我们对画笔只要有以下 4 种设置,基本上就够用了。
mPaint = new Paint();//初始化
mPaint.setColor(Color.RED);//设置颜色
mPaint.setAntiAlias(true);//设置抗锯齿
mPaint.setStyle(Paint.Style.FILL);//描边效果
mPaint.setStrokeWidth(4);//描边宽度
下面简单的说明一下这 4 种方法的使用。
1.1 画笔颜色
对于设置画笔颜色,最常见的是 setColor()
方法,但是也一些别的设置颜色的方法,例如下面这样的:
mPaint.setARGB(255, 255, 255, 0);//设置Paint对象颜色,范围为0~255
mPaint.setAlpha(200);//设置alpha不透明度,范围为0~255
//使用系统内置的颜色资源
int color = context.getResources().getColor(R.color.colorPrimary);
mPaint.setColor(color);
1.2 设置抗锯齿
设置抗锯齿的代码就一行,一般情况下都要写。开启抗锯齿的原因是修改图形边缘处的像素颜色,从而让图形在肉眼看来具有更加平滑的感觉。大多数情况下,都需要开启,给个效果图解释一下:
1.3 设置描边宽度
mPaint.setStrokeWidth()
方法中填入的是数值,也就是你画笔的宽度,最简单的说法就是你如果画了一条线,此方法中填入的数值 越大
,你所画的线 越宽
。给一张图说明一下:
在画笔宽度为 0 的情况下,使用 drawLine 或者使用描边模式 (STROKE) 也可以绘制出内容。只是绘制出的内容始终是 1 像素,不受画布缩放的影响。该模式被称为hairline mode
(发际线模式)。
1.4 设置描边效果
mPaint.setStyle()
这个方法中传入的参数有三个,可以点击进入源码查看一下,给一张图说明一下这三个参数所代表的的不同含义:
在这里设置了描边的宽度较大值,这样显示出来的效果比较明显,红色表示圆的实际大小。
除了上面的这 4 种最常见的画笔的设置,其实还有一些不太常用的画笔的设置,就简单介绍一下,了解即可。
二、 不太常见的效果类的画笔设置
2.1 设置画笔线帽
mPaint.setStrokeCap(Paint.Cap.BUTT); //无线帽
mPaint.setStrokeCap(Paint.Cap.SQUARE);//方形线帽
mPaint.setStrokeCap(Paint.Cap.ROUND); //圆角效果
效果图:
2.2 设置线段连接方式
mPaint.setStrokeJoin(Paint.Join.MITER);//尖角(默认模式)
mPaint.setStrokeJoin(Paint.Join.BEVEL);//平角
mPaint.setStrokeJoin(Paint.Join.ROUND);//圆角
效果图:
2.3 斜接模式长度限制
Android 中线段连接方式默认是 MITER
,即在拐角处延长外边缘,直到相交位置。当连接角度小于一定程度时会自动将连接模式转换为 BEVEL
(平角)。这个角度大约是 28.96°。即 MITER
(尖角) 模式下小于该角度的线段连接方式会自动转换为 BEVEL
(平角) 模式。我们可以通过下面的方法来更改默认限制:
// 参数 miter 就是对长度的限制,
// 它可以通过这个公式计算:miter = 1 / sin ( angle / 2 ) ,
// angel 是两条线的形成的夹角
// 这个参数的默认值是 4
paint.setStrokeMiter(10);
2.4 双线性过滤
图像在放大绘制的时候,默认使用的是最近邻插值过滤,这种算法简单,但会出现马赛克现象;而如果开启了双线性过滤,就可以让结果图像显得更加平滑。通过以下代码设置:
mPaint.setFilterBitmap(true);
效果图:
2.5 PathEffect
什么是 PathEffect
?它是用来给图形的轮廓设置效果。对 Canvas
所有的图形绘制有效。总共有 6 种,如下表格显示:
名称 | 说明 |
---|---|
CornerPathEffect | 圆角效果,将尖角替换为圆角。 |
DashPathEffect | 虚线效果,用于各种虚线效果。 |
PathDashPathEffect | Path 虚线效果,虚线中的间隔使用 Path 代替。 |
DiscretePathEffect | 把线条进行随机的偏离,让轮廓变得乱七八糟。乱七八糟的方式和程度由参数决定 |
SumPathEffect | 两个 PathEffect 效果组合,同时绘制两种效果。 |
ComposePathEffect | 两个 PathEffect 效果叠加,先使用效果1,之后使用效果2。 |
最常用的是前两种,例如第一种:
RectF rect = new RectF(0, 0, 600, 600);
float corner = 300;
// 使用 CornerPathEffect 实现类圆角效果
canvas.translate((1080 - 600) / 2, (1920 / 2 - 600) / 2);
mPaint.setPathEffect(new CornerPathEffect(corner));
canvas.drawRect(rect, mPaint);
效果如图:
例如第二种:
Path path_dash = new Path();
path_dash.lineTo(0, 1720);
canvas.save();
canvas.translate(980, 100);
/**
* intervals[]:
* 间隔,用于控制虚线显示长度和隐藏长度,它必须为偶数(且至少为 2 个),
* 按照[显示长度,隐藏长度,显示长度,隐藏长度]的顺序来显示。
*
* phase:
* 相位(和正余弦函数中的相位类似,周期为intervals长度总和),
* 也可以简单的理解为偏移量。
*/
mPaint.setPathEffect(new DashPathEffect(new float[]{200, 100}, 0));
canvas.drawPath(path_dash, mPaint);
canvas.restore();
canvas.save();
canvas.translate(400, 100);
mPaint.setPathEffect(new DashPathEffect(new float[]{200, 100}, 100));
canvas.drawPath(path_dash, mPaint);
canvas.restore();
效果如图:
2.6 setMaskFilter
这个方法的作用是给绘制内容的上方添加效果。写法如下
/**
* radius: 模糊半径
* NORMAL: 内外都模糊绘制
* SOLID: 内部正常绘制,外部模糊
* INNER: 内部模糊,外部不绘制
* OUTER: 内部不绘制,外部模糊
*/
mPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.NORMAL));//设置画笔遮罩滤镜 ,传入度数和样式
效果图就盗用一张:
到此,上面介绍的都是 效果类
的 Paint
的各种 api
的使用方式,下面着重介绍一下颜色类的 Paint
的各种高级 api
的使用方式。
三 、颜色类效果
3.1 渲染器
通过 mPaint.setShader(mShader)
给画笔设置颜色,它和直接设置颜色的区别是,渲染器设置的是一个颜色方案,或者说是一套着色规则。当设置了 mShader
之后,Paint
在绘制图形和文字时就不使用 setColor/ARGB()
设置的颜色了,而是使用 mShader
的方案中的颜色。
方法中传入的参数 mShader
类有 4 个子类,我们使用的也是这四个子类,先给一张效果图展示一下:
下面贴出代码,注释都写在代码里了,应该很好理解的:
/**
* 1.线性渲染,LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[], @Nullable float positions[], @NonNull TileMode tile)
* (x0,y0):渐变起始点坐标
* (x1,y1):渐变结束点坐标
* color0:渐变开始点颜色,16进制的颜色表示,必须要带有透明度
* color1:渐变结束颜色
* colors:渐变数组
* positions:位置数组,position的取值范围[0,1],作用是指定某个位置的颜色值,如果传null,渐变就线性变化。
* tile:用于指定控件区域大于指定的渐变区域时,空白区域的颜色填充方法
*/
linearShader = new LinearGradient(30, 30, 400, 400, new int[]{Color.RED, Color.BLUE, Color.GREEN}, new float[]{0.f, 0.7f, 1}, Shader.TileMode.REPEAT);
mPaint.setShader(linearShader);
canvas.drawRect(30,30,400,400, mPaint);
/**
* 环形渲染,RadialGradient(float centerX, float centerY, float radius, @ColorInt int colors[], @Nullable float stops[], TileMode tileMode)
* centerX ,centerY:shader的中心坐标,开始渐变的坐标
* radius:渐变的半径
* centerColor,edgeColor:中心点渐变颜色,边界的渐变颜色
* colors:渐变颜色数组
* stops:渐变位置数组,类似扫描渐变的positions数组,取值[0,1],中心点为0,半径到达位置为1.0f
* tileMode:shader未覆盖以外的填充模式。
*/
radialShader = new RadialGradient(700, 230, 200, new int[]{Color.GREEN, Color.YELLOW, Color.RED}, null, Shader.TileMode.CLAMP);
mPaint.setShader(radialShader);
canvas.drawCircle(700, 230, 200, mPaint);
/**
* 扫描渲染,SweepGradient(float cx, float cy, @ColorInt int color0,int color1)
* cx,cy 渐变中心坐标
* color0,color1:渐变开始结束颜色
* colors,positions:类似LinearGradient,用于多颜色渐变,positions为null时,根据颜色线性渐变
*/
sweepShader = new SweepGradient(230, 800, Color.RED, Color.GREEN);
mPaint.setShader(sweepShader);
canvas.drawCircle(230, 800, 200, mPaint);
/**
* 位图渲染,BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY)
* Bitmap:构造shader使用的bitmap
* tileX:X轴方向的TileMode
* tileY:Y轴方向的TileMode
* Shader.TileMode:
* REPEAT, 绘制区域超过渲染区域的部分,重复排版
* CLAMP, 绘制区域超过渲染区域的部分,会以最后一个像素拉伸排版
* MIRROR, 绘制区域超过渲染区域的部分,镜像翻转排版
*/
bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);
mPaint.setShader(bitmapShader);
canvas.drawRect(500,600,1000, 1000, mPaint);
/**
* 组合渲染,
* ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, Xfermode mode)
* ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, PorterDuff.Mode mode)
* shaderA,shaderB:要混合的两种shader
* Xfermode mode: 组合两种shader颜色的模式
* PorterDuff.Mode mode: 组合两种shader颜色的模式
*/
BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
LinearGradient linearGradient = new LinearGradient(350, 1300, 850, 1700, new int[]{Color.RED, Color.GREEN, Color.BLUE}, null, Shader.TileMode.CLAMP);
mComposeShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY);
mPaint.setShader(mComposeShader);
canvas.drawCircle(500, 1500, 250, mPaint);
3.2 图层混合模式
在上面的组合渲染中,有一个参数 PorterDuff.Mode
,也就是图层混合模式。
这个图层混合是什么意思呢? 简单说就是将你要所绘制的图形的像素与 Canvas
画布中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新 Canvas
中最终的像素颜色值。
那么这个图层混合会在什么地方用到呢?一共有三处:
- 组合渲染
ComposeShader
中会使用到。 - 画笔
Paint.setXfermode()
方法中会使用到。 PorterDuffColorFilter
中会使用到。
第一种已经介绍过了,现在来说说第二种如何使用,先说一下图层混合一共有 18
种模式:
//所绘制不会提交到画布上
new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
//显示上层绘制的图像
new PorterDuffXfermode(PorterDuff.Mode.SRC),
//显示下层绘制图像
new PorterDuffXfermode(PorterDuff.Mode.DST),
//正常绘制显示,上下层绘制叠盖
new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
//上下层都显示,下层居上显示
new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
//取两层绘制交集,显示上层
new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
//取两层绘制交集,显示下层
new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
//取上层绘制非交集部分,交集部分变成透明
new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
//取下层绘制非交集部分,交集部分变成透明
new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
//取上层交集部分与下层非交集部分
new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
//取下层交集部分与上层非交集部分
new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
//去除两图层交集部分
new PorterDuffXfermode(PorterDuff.Mode.XOR),
//取两图层全部区域,交集部分颜色加深
new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
//取两图层全部区域,交集部分颜色点亮
new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
//取两图层交集部分,颜色叠加
new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
//取两图层全部区域,交集部分滤色
new PorterDuffXfermode(PorterDuff.Mode.SCREEN),
//取两图层全部区域,交集部分饱和度相加
new PorterDuffXfermode(PorterDuff.Mode.ADD),
//取两图层全部区域,交集部分叠加
new PorterDuffXfermode(PorterDuff.Mode.OVERLAY)
展示一下效果图:
以上就是 18
种图层混合的效果图,这个东西知道即可,以后如果要做特定效果,知道有这么个方法就行。
同时,如果你想要使用图层混合模式,也就是方法二中的 Paint.setXfermode()
正常绘制,必须使用离屏缓存 (Off-screen Buffer) 把内容绘制在额外的层上,再把绘制好的内容贴回 View
中。如下图效果:
想要使用 离屏缓冲
,有 2
种方式,一般情况下,使用第 1
种方式:
saveLayer()
: 可以做短时的离屏缓冲。使用方法很简单,在绘制代码的前后各加一行代码,在绘制之前保存,绘制之后恢复。
int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(rectBitmap, 0, 0, paint); // 画方
paint.setXfermode(xfermode); // 设置 Xfermode
canvas.drawBitmap(circleBitmap, 0, 0, paint); // 画圆
paint.setXfermode(null); // 用完及时清除 Xfermode
canvas.restoreToCount(saved);
View.setLayerType()
//直接把整个 View 都绘制在离屏缓冲中。
View.setLayerType()
//使用 GPU 来缓冲。
setLayerType(LAYER_TYPE_HARDWARE)
//直接直接用一个 Bitmap 来缓冲。
setLayerType(LAYER_TYPE_SOFTWARE)
3.3 滤镜效果
滤镜大家应该都知道,相机里面一般都可以选择滤镜效果,其实就是对颜色再进一步处理,这里介绍常见的 3
种滤镜效果。
先来一张效果图展示一下:
上面三种都是使用 第一种 LightingColorFilter
这个类完成的,作用是模拟简单的光照效果。给出代码,根据注释可以简单的改变颜色:
/**
* R' = R * mul.R / 0xff + add.R
* G' = G * mul.G / 0xff + add.G
* B' = B * mul.B / 0xff + add.B
*/
//原始图片效果
LightingColorFilter lighting = new LightingColorFilter(0xffffff,0x000000);
mPaint.setColorFilter(lighting);
canvas.drawBitmap(mBitmap, 100,50, mPaint);
//红色去除掉
LightingColorFilter noRedLighting = new LightingColorFilter(0x00ffff,0x000000);
mPaint.setColorFilter(noRedLighting);
canvas.drawBitmap(mBitmap, 100,750, mPaint);
//绿色更亮
LightingColorFilter greenMoreLighting = new LightingColorFilter(0xffffff,0x003000);
mPaint.setColorFilter(greenMoreLighting);
canvas.drawBitmap(mBitmap, 100,1450, mPaint);
第二种是 PorterDuffColorFilter
,作用是使用一个指定的颜色和一种指定的PorterDuff.Mode
来与绘制对象进行合成。这个比较简单,直接贴代码:
PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DARKEN);
mPaint.setColorFilter(porterDuffColorFilter);
canvas.drawBitmap(mBitmap, 100, 0, mPaint);
第三种就比较厉害了,用的是 ColorMatrixColorFilter 类,这是一个 4x5 的矩阵,先来一个现实原图的看一下:
```java
float[] colorMatrix = {
1,0,0,0,0, //red
0,1,0,0,0, //green
0,0,1,0,0, //blue
0,0,0,1,0 //alpha
};
mColorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
mPaint.setColorFilter(mColorMatrixColorFilter);
canvas.drawBitmap(mBitmap, 100, 0, mPaint);
这个显示原图,如何修改,只需要修改其中的表示 1 的值,如果红色要加强,就将表示红色的 1 改为更高的数字,去掉红色则将 1 改为 0 就行了。
其实还有另外一种写法,如下:
ColorMatrix cm = new ColorMatrix();
mColorMatrixColorFilter = new ColorMatrixColorFilter(cm);
mPaint.setColorFilter(mColorMatrixColorFilter);
canvas.drawBitmap(mBitmap, 100, 0, mPaint);
这个也是显示原图的,其中调节亮度,调用这个方法:
//什么都不改
cm.setScale(1,1,1,1);
//绿色加强
cm.setScale(1,2,1,1);
它还有一个增强饱和度的方法:
//饱和度调节0-无色彩, 1- 默认效果, >1饱和度加强
cm.setSaturation(2);
显示效果如下:
最后
在接下来的文章中,我会继续以简单通俗的方式给你带来自定义 View
的方方面面,毕竟,我们的目的是在产品和设计面前,腰杆子挺直,面对花里胡哨的效果,有种云淡风轻(zhuang bi
)的状态。