专治花里胡哨(三)征服自定义View,画笔 Paint 的各种详细全面的使用方法

好记性不如烂博客,古人诚不欺我。


看本篇文章前,请先阅读治花里胡哨(二)征服自定义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虚线效果,用于各种虚线效果。
PathDashPathEffectPath 虚线效果,虚线中的间隔使用 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)的状态。

本人水平有限,如有错误,请在下方评论中指出。


请帮顶 / 评论点赞!因为你们的赞同/鼓励是我写作的最大动力!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值