Canvas和Paint的关系

在对UI绘制流程的分析中,可以知道performDraw()方法最终会调用到View#onDraw()方法,并且会传递Canvas,那么Canvas到底有什么用呢?

Canvas在UI绘制中扮演的是会话角色,我们通常情况下都能够知道使用Canvas去 画圆型,矩形图片等,但是最终其真正的绘制并不是在我们的android 层面进行的。

通过源码分析,Canvas 继承了BaseCanvas

public class Canvas extends BaseCanvas {}

BaseCanvas中,我们可以发现有大量以nDraw开头的方法,比如drawCircle

public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
    throwIfHasHwBitmapInSwMode(paint);
    nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance());
}
private static native void nDrawCircle(long nativeCanvas, float cx, float cy, float radius, long nativePaint);

通过上述代码块我们可以知道,Canvas并不是具体的执行者,而是一个传达着, 在Canvas当中我们会将所有的参数信息设置好,然后交由底层去绘制。

我们通过观察Canvas中的绘制方法,可以发现每一个绘制方法中都有一个Paint类型的参数,那么Paint的职责到底是什么呢?打开Paint的源码,可以看到:

/**
* The Paint class holds the style and color information about how to draw
* geometries, text and bitmaps.
*/
public class Paint {}

注释中描述了Paint 在绘制过程中保存了色彩信息样式信息

Paint基础

Paint方法主要可以抽象成2大类:

设置和获取图像绘制、路径相关数据

设置画笔样式

// 设置画笔填充样式
paint.style = Paint.Style.STROKE

Paint.Style.FILL :填充内部
Paint.Style.FILL_AND_STROKE :填充内部和描边
Paint.Style.STROKE :仅描边
注意 STROKE、FILL_AND_STROKE与FILL模式相比 外轮廓的位置会扩大。

设置画笔宽度

// 设置画笔宽度
paint.strokeWidth = 200.0f

抗锯齿功能

  • 设置抗锯齿
// 开启抗锯齿
paint.isAntiAlias = true
  • 获取是否开启抗锯齿
val isAntiAlias = paint.isAntiAlias

开启抗锯齿功能会消耗较大资源,绘制图形速度会变慢,但开启后绘制图像会平滑一些。

设置线冒样式

// 设置圆形线冒
paint.strokeCap = Paint.Cap.ROUND

Cap.ROUND(圆形线冒)、 Cap.SQUARE(方形线冒) 、Paint.Cap.BUTT(无线冒)

效果图:image-20201210094248494

代码如下:

val paint = Paint()
fun init() {
    // 设置画笔填充样式
    paint.style = Paint.Style.STROKE
    // 开启抗锯齿
    paint.isAntiAlias = true
    // 设置画笔颜色
    paint.color = Color.RED
    // 设置画笔宽度
    paint.strokeWidth = 200.0f
    // 设置圆形线冒
    paint.strokeCap = Paint.Cap.ROUND
}
 
 
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.drawLine(200f, 300f, 400f, 300f, paint)
}

设置连接处线段

// 设置线段链接处的样式
paint.strokeJoin = Paint.Join.MITER

Join.MITER(结合处为锐角)、Join.Round(结合处为圆弧)、Join.BEVEL(结合处为直线)

效果图:image-20201210094852252

代码如下:

// 创建画笔
private val paint: Paint = Paint()
// 创建矩形区域
private val rect: Rect = Rect()
 
 
init {
    // 设置画笔填充样式
    paint.style = Paint.Style.STROKE
    // 开启抗锯齿
    paint.isAntiAlias = true
    // 设置画笔颜色
    paint.color = Color.RED
    // 设置画笔宽度
    paint.strokeWidth = 200.0f
    // 设置线段链接处于样式
    paint.strokeJoin = Paint.Join.ROUND
}
 
 
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    rect.set(200, 300, 400, 400)
    canvas.drawRect(rect, paint)
}

设置画笔倾斜度

// 设置画笔倾斜度
paint.strokeMiter = 0.8f

清空画笔复位

// 画笔复位
paint.reset()

设置外来画笔

// 设置一个新的画笔
paint.set(Paint())

获取与设置alpha值,颜色和ARGB

// 透明度 0 -100
// r、g、b 0-255
paint.setARGB(80,110,234, 125)
 
 
// 设置透明度 0(完全透明) - 100 (不透明)
paint.alpha = 100
// 获取透明度
val alpha = paint.alpha
 
 
// 设置颜色
paint.color = Color.BLUE
// 获取颜色
val color = paint.color

图像抖动处理

  • 开启图像抖动
// 开启图像抖动
paint.isDither = true
  • 获取图像抖动是否开启
// 开启图像抖动
val isDither = paint.isDither

会使绘制出来的图像颜色更加平滑、饱满和图像更加清晰

设置绘制路径效果

// 设置虚线
// intervals: 控制实线和实线之后空白线的宽度(数组长度必须为偶数)
// phase: 将View向”左“偏移phase
paint.pathEffect = DashPathEffect(intervals = floatArrayOf(20f,10f,50f,100f), phase = 15f)

CornerPathEffect ——圆形拐角效果
paint.setPathEffect(new CornerPathEffect(100))
利用半径R=50的圆来代替原来两条直线间的夹角
DashPathEffect ——虚线效果 画同一条线段,偏移值为15
paint.setPathEffect(new DashPathEffect(new float[]{20,10,50,100},15))
intervals[]:表示组成虚线的各个线段的长度;整条虚线就是由intervals[]中这些基本线段循环组成的。比如,我们定义new float[] {20,10}; 那这个虚线段就是由两段线段组成的,第一个可见的线段长为20,每二个线段不可见,长度为10;
phase:开始绘制的偏移值

设置图形重叠时的处理方式

paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)

通过设置xfermode 可以设置许多不同的效果,如 制作橡皮擦, 具体介绍请看 Paint的高级用法

设置MaskFilter

paint.maskFilter = MaskFilter()

可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等 。

/**
* Create a blur maskfilter.
*
* @param radius 阴影的半径
 * @param style  NORMOL -- 整个图像都被模糊掉
 *               SOLID -- 图像边界外产生一层与Paint颜色一致阴影效果,不影响图像的本身
 *               OUTER -- 图像边界外产生一层阴影,并且将图像变成透明效果
 *               INNER -- 在图像内部边沿产生模糊效果
* @return
*/
paint.setMaskFilter(new BlurMaskFilter(50, BlurMaskFilter.Blur.NORMAL));
/**
* Create an emboss maskfilter
*
 * @param direction  指定光源的位置,长度为xxx的数组标量[x,y,z]
 * @param ambient    环境光的因子 (0~1),越接近0,环境光越暗
 * @param specular   镜面反射系数 越接近0,镜面反射越强
* @param blurRadius 模糊半径 值越大,模糊效果越明显
*/
paint.setMaskFilter(new EmbossMaskFilter(new float[]{1,1,1},0.2f,60,80));

设置颜色过滤器

paint.colorFilter = ColorFilter()

可以在绘制颜色时实现不用颜色的变换效果,可以用来设置图像的黑白效果。

设置图像效果

paint.shader = Shader()

使用Shader可以绘制出各种渐变效果。Shader有一些子类,如:LinearGradient。

在图像下设置阴影层

// radius 为阴影的角度
// dx和dy为阴影在x轴和y轴上的距离
// color为阴影的颜色
paint.setShadowLayer(float radius ,float dx,float dy,int color)

设置获取文字相关的

获取字符的行间距

val spacing = paint.fontSpacing

设置和获取字符的间距

// 获取字符的间距 api >= 21
val letterSpacing = paint.letterSpacing
// 设置字符的间距 api >= 21
paint.letterSpacing = 20f

是否有下划线和设置下划线

// 获取是否有下划线
val isUnderlineText = paint.isUnderlineText
// 设置下划线
paint.isUnderlineText = true

获取与设置是否有文本删除线

// 设置文本删除线
paint.isStrikeThruText = true
// 获取文本删除线
val isStrikeThruText = paint.isStrikeThruText

获取与设置文字大小

// 获取文字大小
val textSize = paint.textSize
// 设置文字大小
paint.textSize = 14f

注意: Paint.setTextSize 单位是pxTextView.setTextSize单位是sp

获取与设置字体类型

// 设置字体样式
paint.typeface = Typeface.DEFAULT
val typeface = paint.typeface

默认有四种字体样式: BOLD(加粗、BOLD_ITALIC(加粗并倾斜)、ITALIC(倾斜)、NORMAL(正常)
也可通过Typeface类来自定义个性化字体。

获取与设置文字倾斜

// 设置文字倾斜度
paint.textSkewX = -0.25f
val textSkewX = paint.textSkewX

参数没有具体范围, 官方推荐值为-0.25,值为负则右倾,为正则左倾,默认值为0。

获取与设置文本对齐方式

// 设置文本的对齐方式
paint.textAlign = Paint.Align.CENTER
val textAlign = paint.textAlign

取值为 CENTER、LEFT、RIGHT,即文字绘制是左边对齐、右边还是局中。

亚像素

// 设置亚像素
paint.isSubpixelText = true

固定的几个范围:320480,480800,7201280,10801920等,那么如何在同样的分辨率的显示器中增强显示清晰度呢?
亚像素的概念就油然而生了,亚像素就是把两个相邻的两个像素之间的距离再细分,再插入一些像素,这些通过程序加入的像素就是亚像素。在两个像素间插入的像素个数是通过程序计算出来的,一般是插入两个、三个或四个。 所以打开亚像素显示,是可以在增强文本显示清晰度的,但由于插入亚像素是通过程序计算而来的,所以会耗费一定的计算机性能。

文本折断

paint.breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)

如文本阅读器的翻页效果,我们需要在翻页的时候动态折断或生成一行字符串。