Android中的Canvas — 是来自android.graphics包的一个类,提供了在View上进行2D绘制的API。Canvas就像一块画布,开发者可以在上面使用各种draw*方法绘制几何图形、文本、图像和路径。根据Android Developers Documentation(2025),Canvas是Android中自定义绘制的主要工具,并与Paint类一起使用来设置样式和颜色。每次调用onDraw()都会获得一个已与视图的位图缓冲区关联的Canvas实例。
要点
Canvas — 是Android中核心的2D图形类,提供在平面上绘制的方法。每个Canvas都与一个Bitmap(位图)相关联——这是一块内存区域,绘制操作的结果会写入其中。当View被重新绘制时,系统会将一个已与该View的缓冲区关联的Canvas传递给onDraw()。
Canvas在坐标系统中工作,其中(0,0)是左上角,X轴向右,Y轴向下。所有绘制操作都使用Canvas的当前状态,包括变换矩阵、裁剪区域(clip)和Paint样式。根据Android Documentation(2025),Canvas API包括30多种draw*方法,用于不同类型的图形基元。
自Android 3.0(API 11)起,Canvas通过DisplayList支持硬件加速。在加速模式下,Canvas操作被记录到DisplayList中,然后在GPU上执行。这带来了显著的性能提升,但也施加了限制:某些Canvas操作(带非Path裁剪的clipPath)在加速模式下不被支持。
Canvas提供了一套用于绘制基本基元的方法。每个方法接受几何参数和一个Paint对象,该对象定义了视觉样式。
| 方法 | 用途 | 示例 |
|---|---|---|
| drawRect() | 绘制矩形 | canvas.drawRect(10f, 10f, 100f, 100f, paint) |
| drawCircle() | 绘制圆形 | canvas.drawCircle(50f, 50f, 30f, paint) |
| drawLine() | 绘制直线 | canvas.drawLine(0f, 0f, 100f, 100f, paint) |
| drawText() | 绘制文本 | canvas.drawText("Hello", 10f, 20f, paint) |
| drawBitmap() | 绘制图像 | canvas.drawBitmap(bitmap, 0f, 0f, paint) |
| drawPath() | 绘制任意路径 | canvas.drawPath(path, paint) |
| drawOval() | 绘制椭圆 | canvas.drawOval(rectF, paint) |
| drawArc() | 绘制弧线或扇形 | canvas.drawArc(rectF, 0f, 90f, true, paint) |
每个方法可以按任意顺序调用——Canvas按照调用顺序绘制,将新元素叠加在之前的元素之上。对于复杂构图,使用多层绘制技术,通过保存和恢复Canvas状态来实现。
Paint — 是定义Canvas如何绘制的类。没有Paint,Canvas的方法毫无用处——它设置颜色、线条粗细、填充样式、文本大小、效果(阴影、渐变、模糊)和混合模式。
Paint具有关键属性:color(颜色)、style(FILL、STROKE或FILL_AND_STROKE)、strokeWidth(线条粗细)、textSize(文本大小)、isAntiAlias(抗锯齿)、shader(渐变或图像)。创建Paint对象是一项昂贵的操作,因此应该创建一次并重复使用。
private val fillPaint = Paint().apply {
color = Color.parseColor("#FF6200EE")
style = Paint.Style.FILL
isAntiAlias = true
}
private val strokePaint = Paint().apply {
color = Color.WHITE
style = Paint.Style.STROKE
strokeWidth = 4f
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(100f, 100f, 80f, fillPaint)
canvas.drawCircle(100f, 100f, 80f, strokePaint)
}
在这个例子中使用了两个Paint:一个用于用紫色填充圆形,另一个用于白色描边。将fill和stroke分开是标准做法,可以在不重新创建对象的情况下灵活管理外观。
对于渐变填充,Paint接受一个Shader(着色器)——LinearGradient、RadialGradient、SweepGradient子类。Shader通过setShader()方法附加到Paint,并定义颜色在绘制区域中如何变化。
Canvas支持坐标系统的矩阵变换:translate()(平移)、rotate()(旋转)、scale()(缩放)和skew()(倾斜)。变换会累积:每次后续调用都应用在之前的变换之上。
变换栈的管理通过save()和restore()进行。save()将Canvas的当前状态(矩阵、裁剪区域)保存到栈中,restore()恢复最后保存的状态。在save()和restore()之间可以有嵌套的变换。
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.save() // 保存初始状态
canvas.translate(100f, 100f) // 平移原点
// 第一个正方形
canvas.drawRect(0f, 0f, 50f, 50f, fillPaint)
canvas.save() // 平移后保存
canvas.rotate(45f, 25f, 25f) // 围绕正方形中心旋转
// 旋转的正方形
canvas.drawRect(0f, 0f, 50f, 50f, strokePaint)
canvas.restore() // 旋转后恢复
canvas.restore() // 恢复初始状态
}
始终在开始变换之前使用save(),在完成变换之后使用restore()。不平衡的save/restore会导致矩阵累积以及后续元素的错误绘制。
对于复杂图形,Canvas与Bitmap和Path结合使用。Bitmap用于加载图像和缓存渲染好的帧。Path用于构建任意贝塞尔曲线、多边形和复杂轮廓。
离屏渲染技术:创建一个Bitmap,通过单独的Canvas在其上绘制复杂场景,然后通过drawBitmap()在onDraw()中显示准备好的Bitmap。这允许更新复杂图形而无需完全重绘每一帧。
private var offscreenBitmap: Bitmap? = null
private var offscreenCanvas: Canvas? = null
fun buildOffscreenScene(width: Int, height: Int) {
offscreenBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
offscreenCanvas = Canvas(offscreenBitmap)
// 渲染场景一次
offscreenCanvas!!.drawColor(Color.WHITE)
drawGrid(offscreenCanvas!!)
drawComplexShape(offscreenCanvas!!)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
offscreenBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
}
Path用于构建复杂轮廓:贝塞尔曲线(quadTo、cubicTo)、弧线(arcTo)、直线(lineTo)。Path可以是闭合的(close())用于填充图形,或开放的用于线条。主要的drawPath()方法将Path和Paint结合起来进行最终绘制。
Canvas绘制的性能直接影响应用程序的帧率。主要问题:onDraw()中的过多分配、频繁的invalidate()以及对硬件加速的非最优使用。
Paint、Path、Rect、RectF和其他对象应在初始化器或setup()方法中一次性创建。onDraw()中的每次分配都会增加垃圾回收器的负担,导致微卡顿。例外——static或companion object用于不可变对象。
如果View的很大部分隐藏在可见区域之外(例如在ScrollView中),使用canvas.clipRect()来限制绘制。硬件加速在这种情况下会进一步优化不可见像素的裁剪。
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 裁剪到可见区域
canvas.save()
canvas.clipRect(scrollX, scrollY, scrollX + width, scrollY + height)
// 渲染大场景
renderScene(canvas)
canvas.restore()
}
对于具有不常更新的复杂Canvas图形的View,使用View.setLayerType()配合LAYER_TYPE_HARDWARE。对于频繁更新的动画,使用LAYER_TYPE_NONE以避免昂贵的硬件层更新。通过GPU Profile Rendering和Android Studio Profiler进行性能分析。
常见问题
是的,可以创建Canvas(Bitmap)——接受Bitmap的构造函数。这用于离屏渲染:在位图上绘制,然后通过drawBitmap()显示。Canvas也可以从Surface(用于SurfaceView)或通过Picture创建。
Canvas — 是在UI线程中在View上绘制的API。SurfaceView — 是一个独立的Surface,可以通过自己的Canvas(lockCanvas() / unlockCanvasAndPost())从后台线程更新。SurfaceView更适用于游戏和视频。
最常见的原因是save()和restore()不匹配。如果变换(translate、rotate)后没有调用restore(),Canvas会保持在变换后的坐标系统中,后续元素会偏移绘制。检查save()/restore()对。
使用Paint.setTextAlign(Paint.Align.CENTER)进行水平对齐,使用Paint.getTextBounds()或Paint.descent() / ascent()进行垂直对齐。居中:canvas.drawText(text, centerX, centerY - (ascent + descent) / 2, paint)。
Canvas本身不制作动画——它只绘制当前状态。动画通过连续调用invalidate()(或postInvalidateOnAnimation())实现,每次在onDraw()中绘制新帧。为获得流畅的动画,使用ValueAnimator或Choreographer与Vsync同步。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。