第2章:Kotlin绘图基础:Canvas与Paint对象详解、坐标系与屏幕坐标、绘制基本图形
各位同学,欢迎来到第二章。上一章我们搭好了开发环境,也跑通了第一个Demo。今天咱们来点真家伙——Canvas和Paint,这两个家伙是Android绘图的左膀右臂。我刚开始学的时候,总觉得它们像黑魔法,后来才发现,说白了就是「画布」和「画笔」的关系。
2.1 Canvas与Paint:绘图界的「纸」和「笔」
先问大家一个问题:你小时候在纸上画画,需要什么?一张纸,一支笔,对吧?Canvas就是那张纸,Paint就是那支笔。只不过在Android里,这张纸是虚拟的,这支笔可以调粗细、颜色、样式。
Canvas 负责「怎么画」——画线、画圆、画矩形,都是它的方法。
Paint 负责「画成什么样」——红色还是蓝色?实线还是虚线?要不要抗锯齿?
我个人习惯把Paint比作「瑞士军刀」,因为它能调的东西太多了。咱们先看一个最简单的例子:
// 创建一个画笔
val paint = Paint().apply {
color = Color.RED // 红色
strokeWidth = 5f // 线宽5像素
style = Paint.Style.STROKE // 只画轮廓,不填充
isAntiAlias = true // 开启抗锯齿,边缘更平滑
}
// 在Canvas上画一条线
canvas.drawLine(100f, 100f, 500f, 100f, paint)
嗯,这里要注意:isAntiAlias 这个属性,我建议你永远设为 true。为什么?因为不开抗锯齿,斜线会有明显的锯齿感,像狗啃的一样。我在项目中遇到过,用户反馈说「这个圆怎么有毛刺」,其实就是忘了开抗锯齿。
2.2 坐标系与屏幕坐标:别搞反了Y轴
Android的坐标系,跟数学课上学的有点不一样。数学课上,Y轴向上是正方向。但在Android里,Y轴向下是正方向。你想想看,手机屏幕左上角是(0,0),往右X增加,往下Y增加。
我第一次写自定义View时,想画一个从左上角到右下角的对角线,结果画反了。后来才意识到,Y轴方向搞反了。
来看一张坐标示意图(用文字描述):
(0,0) ——————→ X 正方向
|
|
↓
Y 正方向
屏幕坐标有几个关键点:
- 原点:屏幕左上角 (0, 0)
- X轴:向右为正
- Y轴:向下为正
- 宽高:通过
canvas.width和canvas.height获取
fun Float.toScreenY(canvasHeight: Float) = canvasHeight - this,把数学Y转换成屏幕Y。这个习惯我一直保留到现在。
2.3 绘制基本图形:点、线、矩形、圆、椭圆
好了,理论说完了,咱们动手画点东西。我习惯先画一个「Hello World」级别的图形集合,这样能快速验证Canvas和Paint是否配合正常。
2.3.1 画点
画点用 drawPoint,但要注意,点其实是一个小圆。如果你把线宽设大一点,点看起来就是个实心圆。
val pointPaint = Paint().apply {
color = Color.BLUE
strokeWidth = 10f // 点的大小由线宽决定
style = Paint.Style.FILL
isAntiAlias = true
}
canvas.drawPoint(200f, 300f, pointPaint)
2.3.2 画线
画线用 drawLine,四个参数分别是:起点X、起点Y、终点X、终点Y。
val linePaint = Paint().apply {
color = Color.GREEN
strokeWidth = 3f
style = Paint.Style.STROKE
}
canvas.drawLine(50f, 50f, 400f, 50f, linePaint) // 水平线
canvas.drawLine(50f, 50f, 50f, 400f, linePaint) // 垂直线
如果想画多条连续的线,可以用 drawLines,传入一个float数组,两两一组:
val points = floatArrayOf(
100f, 100f, // 点1
200f, 200f, // 点2
300f, 150f, // 点3
400f, 250f // 点4
)
canvas.drawLines(points, linePaint)
2.3.3 画矩形
矩形用 drawRect,参数是左上角和右下角的坐标。也可以传一个 RectF 对象。
val rectPaint = Paint().apply {
color = Color.MAGENTA
style = Paint.Style.FILL
}
canvas.drawRect(100f, 100f, 300f, 200f, rectPaint) // 实心矩形
// 空心矩形
rectPaint.style = Paint.Style.STROKE
rectPaint.strokeWidth = 4f
canvas.drawRect(350f, 100f, 550f, 200f, rectPaint)
2.3.4 画圆
画圆用 drawCircle,三个参数:圆心X、圆心Y、半径。
val circlePaint = Paint().apply {
color = Color.CYAN
style = Paint.Style.FILL
isAntiAlias = true
}
canvas.drawCircle(200f, 300f, 100f, circlePaint) // 圆心(200,300),半径100
画空心圆也很简单,把style改成STROKE就行:
circlePaint.style = Paint.Style.STROKE
circlePaint.strokeWidth = 8f
canvas.drawCircle(450f, 300f, 100f, circlePaint)
2.3.5 画椭圆
椭圆用 drawOval,参数是一个 RectF,椭圆会被限制在这个矩形框内。
val ovalPaint = Paint().apply {
color = Color.YELLOW
style = Paint.Style.FILL
isAntiAlias = true
}
val ovalRect = RectF(100f, 400f, 300f, 500f) // 宽200,高100
canvas.drawOval(ovalRect, ovalPaint)
你想想看,如果这个矩形是正方形,画出来的椭圆就是圆。所以圆其实是椭圆的特例。
2.4 综合示例:画一个小房子
咱们把上面学的综合一下,画一个简单的小房子。这个例子我在教新同事时经常用,能快速检验基本功。
// 在自定义View的onDraw中
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 1. 画房子主体(矩形)
val housePaint = Paint().apply {
color = Color.rgb(200, 150, 100) // 棕色
style = Paint.Style.FILL
}
canvas.drawRect(100f, 200f, 400f, 450f, housePaint)
// 2. 画屋顶(三角形,用三条线)
val roofPaint = Paint().apply {
color = Color.RED
strokeWidth = 5f
style = Paint.Style.STROKE
}
canvas.drawLine(100f, 200f, 250f, 100f, roofPaint) // 左斜边
canvas.drawLine(250f, 100f, 400f, 200f, roofPaint) // 右斜边
canvas.drawLine(100f, 200f, 400f, 200f, roofPaint) // 底边
// 3. 画门(矩形)
val doorPaint = Paint().apply {
color = Color.rgb(100, 50, 0) // 深棕色
style = Paint.Style.FILL
}
canvas.drawRect(220f, 320f, 280f, 450f, doorPaint)
// 4. 画窗户(圆)
val windowPaint = Paint().apply {
color = Color.CYAN
style = Paint.Style.FILL
}
canvas.drawCircle(150f, 280f, 30f, windowPaint)
canvas.drawCircle(350f, 280f, 30f, windowPaint)
}
运行一下,你应该能看到一个棕色的小房子,红色屋顶,一扇门,两扇圆窗户。虽然简陋,但五脏俱全。
2.5 本章小结
这一章我们干了三件事:
- 认识了Canvas(画布)和Paint(画笔)这对黄金搭档
- 搞清楚了Android坐标系——Y轴向下为正,别搞反了
- 亲手画了点、线、矩形、圆、椭圆,还搭了个小房子
下一章,咱们要玩点更高级的——路径(Path)和贝塞尔曲线。到时候你会发现,原来那些圆润的图标、流畅的动画,背后都是这些基础知识的组合。嗯,先消化今天的内容,动手敲一遍代码,比看十遍都管用。
记住:绘图没有捷径,多画就对了。