参考:
- https://blog.csdn.net/harvic880925/article/details/38875149
- https://www.jianshu.com/p/f2b14c994f0f
- https://blog.csdn.net/harvic880925/article/details/38926877
关于绘图相关的 Paint、Canvas多少都接触过一些,但没有系统的学习过,每次都是边查边用,这里都是参考大神的博客而成的学习记录,(采用Kotlin语言,来编写,kotlin确实有些坑,但不妨碍学习),同时将心得与组内开发人员一起学习,分享;
1. Paint与Canvas
Paint是画笔,Canvas是布,程序中为画布;
- 跟要要画的东西的设置相关的,比如大小,粗细,画笔颜色,透明度,字体的样式等等,都是在Paint里设置;
- 凡是要画出成品的东西,比如圆形,矩形,文字等相关的都是在Canvas里生成;
Paint 基本设置函数:
- paint.setAntiAlias(true);//抗锯齿功能
- paint.setColor(Color.RED); //设置画笔颜色
- paint.setStyle(Style.FILL);//设置填充样式,Style.STOKE 为空心
- paint.setStrokeWidth(30);//设置画笔宽度
- paint.setShadowLayer(10, 15, 15, Color.GREEN);//设置阴影
以上几个方法除setShadowLayer
都好理解,不做过多解释;setShadowLayer
签名为:
/**
* radius:阴影的倾斜度
* dx:水平位移
* dy:垂直位移
*/
public void setShadowLayer(float radius, float dx, float dy, int shadowColor)
Canvas的基本设置:
- canvas.drawColor(Color.BLUE);
- canvas.drawRGB(255, 255, 0); //这两个功能一样,都是用来设置背景颜色
2. 基本几何图形绘制
单条直线与多条直线
我们知道,确定一条直线,需要2个点,所以此方法API与此定义一致如:
// 方法签名
public void drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint) {...}
// 使用
canvas.drawLine(0f,0f, 100f, 10f, paint)
多条直线
public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint)
public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
@NonNull Paint paint)
参数:
- pts为一系列点的集合,至少要有4个点,他们不是连接线,而是每2个点形成一条直线;
// multi line
val pts = floatArrayOf(200f, 350f, 230f, 400f, 250f, 450f, 300f, 500f)
canvas.drawLines(pts, paint)
单个点与多个点
// 单个点
public void drawPoint(float x, float y, @NonNull Paint paint)
// 多个点
public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint)
public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
@NonNull Paint paint)
参数:
- offset:集合中跳过的数值个数,注意不是点的个数!一个点是两个数值;
- count:参与绘制的数值的个数,指pts[]里人数值个数,而不是点的个数,因为一个点是两个数值;
val paint2 = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.GREEN
style = Paint.Style.FILL
strokeWidth = 20f
}
canvas.drawPoint(500f, 500f, paint2)
val points = floatArrayOf(550f, 550f, 550f, 600f, 500f, 650f, 500f, 700f)
// 跳过2个数值,然后用后面6个点,来画poiner,也就是最后3个点,
// 如果count传入4,即最后一个不画,传大于6的数,奔溃(ArrayIndexOutOfBoundsException),
// 因为8个数,除去2个数外,只有6个数了;
canvas.drawPoints(points, 2, 6, paint2)
矩形工具类RectF与Rect
根据四个点构建一个矩形结构;在画图时,利用这个矩形结构可以画出对应的矩形或者与其它图形Region相交、相加等等;
Region
部分,后面细说;
圆角矩形
public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)
参数:
- rx,ry 表示生成圆角的椭圆的X|Y轴半径
画圆
很好理解,确定圆心与半径;
椭圆
椭圆是根据矩形生成的,以矩形的长为椭圆的X轴,矩形的宽为椭圆的Y轴,建立的椭圆图形;
val paint = Paint().apply {
color = Color.RED
style = Paint.Style.STROKE//填充样式改为描边
strokeWidth = 5f//设置画笔宽度
}
val rect = RectF(100f, 10f, 600f, 300f)
canvas.drawRect(rect, paint)//画矩形
paint.color = Color.GREEN//更改画笔颜色
canvas.drawOval(rect, paint)//同一个矩形画椭圆
弧
弧是椭圆的一部分,而椭圆是根据矩形来生成的,所以弧也是根据矩形来生成的;
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle,
boolean useCenter, @NonNull Paint paint)
参数:
- RectF oval:生成椭圆的矩形
- float startAngle:弧开始的角度,以X轴正方向为0度
- float sweepAngle:弧持续的角度
- boolean useCenter:是否有弧的两边,True,还两边,False,只有一条弧
val paint = Paint().apply {
color = Color.RED
style = Paint.Style.STROKE//填充样式改为描边
strokeWidth = 2f//设置画笔宽度
}
val rect = RectF(20f, 10f, 600f, 300f)
canvas.drawRect(rect, paint)
paint.color = Color.GREEN
paint.strokeWidth = 5f
canvas.drawArc(rect, 0f, 90f, true, paint)
canvas.drawArc(rect, 180f, 90f, false, paint)
画笔描边效果:
画笔填充效果
路径 path
使用 canvas.drawPath(path, paint)
画
直线路径
- void moveTo (float x1, float y1): 表示直线的开始点;
- void lineTo (float x2, float y2):直线的结束点,又是下一次绘制直线路径的开始点;lineTo()可以一直用;
- void close ():将路径首尾点连接起来,形成闭环;
// 画一个三角形
val path = Path().apply {
moveTo(10f, 10f)
lineTo(10f, 100f)//第一条直线的终点,也是第二条直线的起点
lineTo(300f, 100f)//画第二条直线
close() //形成闭环
}
canvas.drawPath(path, paint)
画一个气泡图:
https://www.jianshu.com/p/40abd770d05c
矩形路径
void addRect (float left, float top, float right, float bottom, Path.Direction dir)
void addRect (RectF rect, Path.Direction dir)
参数其中Path.Direction有两个值:
Path.Direction.CCW:是counter-clockwise缩写,指创建逆时针方向的矩形路径;
Path.Direction.CW:是clockwise的缩写,指创建顺时针方向的矩形路径;
圆角矩形路径
void addRoundRect (RectF rect, float[] radii, Path.Direction dir)
void addRoundRect (RectF rect, float rx, float ry, Path.Direction dir)
两个画圆角函数,部分参数说明如下:
- float[] radii:必须传入8个数值,分四组,分别对应每个角所使用的椭圆的横轴半径和纵轴半径,如{x1,y1,x2,y2,x3,y3,x4,y4},其中,x1,y1对应第一个角的(左上角)用来产生圆角的椭圆的横轴半径和纵轴半径,其它类推……
- 第二个构造函数:只能构建统一圆角大小
- rx/ry:所产生圆角的椭圆的横/纵轴半径;
圆形、椭圆、弧等等
参考源博客
文字
涉及画文字相关的API比较多;
画笔设置
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
// 1.基本设置
style = Paint.Style.FILL;//绘图样式,对于设文字和几何图形都有效
textAlign = Paint.Align.CENTER;//设置文字对齐方式,取值:align.CENTER、align.LEFT或align.RIGHT
textSize = 12f;//设置文字大小
// 2.样式设置
isFakeBoldText = true // 粗体 bold
isUnderlineText = true // 下划线
textSkewX = 0.25f // 设置字体水平倾斜度
isStrikeThruText = true // 删除线
// 3.其他
textScaleX = 2f // 水平方向拉伸
}
画文字
public void drawText(String text, float x, float y, Paint paint) {
参数说明:
- x : LEFT 默认,字符串的左边在屏幕的位置,CENTER 字符串的中心,RIGHT,右边
- y:是指定这个字符baseline在屏幕上的位置,y不是这个字符中心在屏幕上的位置,而是baseline在屏幕上的位置
例子1:绘图样式
paint.textSize = 38f
paint.color = Color.RED
paint.strokeWidth = 2f //设置画笔宽度,必须要设置,否则设置style无效
paint.textAlign = Paint.Align.LEFT
paint.style = Paint.Style.FILL
canvas.drawText("填充(FILL) ==》Kotlin 重新学Android", 2f, 100f, paint)
paint.style = Paint.Style.STROKE
canvas.drawText("描边(STROKE) ==》Kotlin 重新学Android", 2f, 200f, paint)
paint.style = Paint.Style.FILL_AND_STROKE
canvas.drawText("填充&描边(FILL_AND_STROKE) ==》Kotlin 重新学Android", 2f, 300f, paint)
例子2:样式与倾斜度与水平拉伸
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
textSize = 38f
color = Color.RED
strokeWidth = 2f
textAlign = Paint.Align.LEFT
style = Paint.Style.FILL
// 样式设置
isFakeBoldText = true
isUnderlineText = true
isStrikeThruText = true
}
// ======= 水平倾斜度为:0.25,右斜
paint.textSkewX = -0.25f
canvas.drawText("textSkewX -0.25f ==> Kotlin 重新学Android", 10f, 100f, paint)
paint.textSkewX = 0.25f
canvas.drawText("textSkewX 0.25f ==> Kotlin 重新学Android", 10f, 200f, paint)
canvas.drawLine(0f, 235f, width.toFloat(), 235f, paint)
// ======= 水平拉伸
val paint2 = Paint(Paint.ANTI_ALIAS_FLAG).apply {
textSize = 38f
color = Color.RED
strokeWidth = 2f
style = Paint.Style.FILL
}
// 正常绘制
canvas.drawText("Kotlin 重新学Android", 10f, 300f, paint2)
paint2.textScaleX = 2f // 水平拉伸2倍
canvas.drawText("Kotlin 重新学Android", 10f, 400f, paint2)
// 对比
paint2.textScaleX = 1f //
canvas.drawText("Android开发艺术探索", 10f, 500f, paint2)
paint2.textScaleX = 2f
paint2.color = Color.GREEN
canvas.drawText("Android开发艺术探索", 10f, 500f, paint2)
Canvas绘图方式
普通水平方向绘制
void drawText (String text, float x, float y, Paint paint)
void drawText (CharSequence text, int start, int end, float x, float y, Paint paint)
void drawText (String text, int start, int end, float x, float y, Paint paint)
void drawText (char[] text, int index, int count, float x, float y, Paint paint)
参考原博客
沿路径绘制
public void drawTextOnPath(String text, Path path, float hOffset,
float vOffset, Paint paint) { }
参数:
- float hOffset : 与路径起始点的水平偏移距离
- float vOffset : 与路径中心的垂直偏移量
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
textSize = 38f
color = Color.RED
strokeWidth = 2f
style = Paint.Style.STROKE
}
// ==== 沿路径绘制
val text = "风萧萧兮易水寒,壮士一去兮不复返"
val circlePath1 = Path().apply {
addCircle(width / 4.toFloat(), 220f, 180f, Path.Direction.CCW) // 逆時針
}
val circlePath2 = Path().apply {
addCircle(width / 4 * 3.toFloat(), 220f, 180f, Path.Direction.CCW)
}
canvas.drawPath(circlePath1, paint)
canvas.drawPath(circlePath2, paint)
paint.color = Color.GREEN
// 设置0,0 ,注意这里是逆时针画的圆
canvas.drawTextOnPath(text, circlePath1, 0f, 0f, paint)
// 设置了偏移,水平100,垂直 30f
canvas.drawTextOnPath(text, circlePath2, 100f, 30f, paint)
字体样式与设置自定义字体
参考源博客
3.区域 Region
表示的是canvas图层上的某一块封闭的区域;
构造Region
// 基本构造
public Region() //创建一个空的区域
public Region(Region region) //拷贝一个region的范围
public Region(Rect r) //创建一个矩形的区域
public Region(int left, int top, int right, int bottom) //创建一个矩形的区域
// 间接构造
public void setEmpty() //置空
public boolean set(Region region) // 替换
public boolean set(Rect r)
public boolean set(int left, int top, int right, int bottom)
// 根据路径的区域与某区域的交集,构造出新区域
public boolean setPath(Path path, Region clip)
通过Region来画一个矩形
private val region = Region(10, 10, 100, 100)
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeWidth = 2f
style = Paint.Style.FILL;//绘图样式,对于设文字和几何图形都有效
color = Color.RED
}
drawRegion(canvas, region, paint)
private fun drawRegion(canvas: Canvas, rgn: Region, paint: Paint) {
val iter = RegionIterator(rgn)
val r = Rect()
while (iter.next(r)) {
canvas.drawRect(r, paint) // 画出region
}
}
使用setPath()
构造不规则区域
public boolean setPath(Path path, Region clip)
参数:
- path : 路径
- clip:与path所构成的路径取交集,并将两交集设置为最终的区域
// 椭圆路径
val ovalPath = Path()
val rect = RectF(50f, 50f, 200f, 500f)
canvas.drawRect(rect, paint)
ovalPath.addOval(rect, Path.Direction.CCW)
// setPath时,传入一个比椭圆区域小的矩形区域,让其取【交集】
val region = Region()
region.setPath(ovalPath, Region(50, 50, 200, 300))
paint.color = Color.RED
paint.style = Paint.Style.FILL
drawRegion(canvas, region, paint)
矩形集枚举区域——RegionIterator
一定数量的矩形所合成的形状,也可以代表区域的形状。RegionIterator类,实现了获取组成区域的矩形集的功能,RegionIterator两个函数,一个构造函数和一个获取下一个矩形的函数(next);
在Canvas中没有直接绘制Region的函数,要绘制一个区域,就只能通过利用RegionIterator构造矩形集来逼近的显示区域,就是上面的 drawRegion()
方法;
根据区域构建一个矩形集,然后利用next(Rect r)来逐个获取所有矩形,绘制出来,最终得到的就是整个区域;
将上面的画笔Style从FILL改为STROKE,的效果,可以看到很多个rect绘制出来了;
区域的合并、交叉等操作
无论是区域还是矩形,都会涉及到与另一个区域的一些操作,比如取交集、取并集等,涉及到的函数有:
// 并集
public final boolean union(Rect r)
public boolean op(Rect r, Op op) {
public boolean op(int left, int top, int right, int bottom, Op op)
public boolean op(Region region, Op op)
public boolean op(Rect rect, Region region, Op op)
// Op为
public enum Op {
DIFFERENCE(0), //最终区域为region1 与 region2不同的区域
INTERSECT(1), // 最终区域为region1 与 region2相交的区域
UNION(2), //最终区域为region1 与 region2组合一起的区域
XOR(3), //最终区域为region1 与 region2相交之外的区域
REVERSE_DIFFERENCE(4), //最终区域为region2 与 region1不同的区域
REPLACE(5); //最终区域为为region2的区域
}
实例代码:
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeWidth = 2f
color = Color.RED
style = Paint.Style.STROKE
}
val rect1 = Rect(200, 200, 400, 260)
val rect2 = Rect(270, 130, 330, 330)
canvas.let {
it.drawRect(rect1, paint)
it.drawRect(rect2, paint)
}
// 集合操作
val paint2 = Paint().apply {
style = Paint.Style.FILL
color = Color.GREEN
}
val region1 = Region(rect1)
val region2 = Region(rect2)
region1.op(region2, op) // Region.Op.INTERSECT
drawRegion(canvas, region1, paint2)
// 文字说明
paint.textAlign = Paint.Align.CENTER
paint.textSize = 28f
canvas.drawText("横为region1", 300f, 450f, paint)
canvas.drawText("竖为region2", 300f, 480f, paint)
canvas.drawText("操作为:region1.op(region2, op)", 300f, 510f, paint)
补集有点不好理解
Region其他方法
查看源博客
https://blog.csdn.net/harvic880925/article/details/39056701