在 Android 开发中,用户界面(UI)的定制化是提升应用体验的重要手段之一。尽管 Android 提供了大量的现成控件和布局组件,但有时我们需要创建一些特定的、无法通过标准控件实现的视图效果。这时,自定义视图就显得尤为重要。
自定义视图不仅能帮助开发者实现高度个性化的 UI 设计,还能优化性能,精确控制绘制细节。在 Android 中,Canvas 类为我们提供了丰富的绘图功能,使得开发者可以轻松地在视图上绘制各种图形。无论是简单的形状、复杂的路径,还是动态的图像处理,Canvas 都能提供强大的支持。
本文将介绍如何在自定义视图中使用 Canvas 绘制基本图形,带你一步步深入理解如何实现各种绘制操作。我们将涵盖从绘制简单的线条和图形,到更复杂的路径和位图绘制等内容,帮助你掌握自定义视图的核心技能。
1. 绘制基本图形
drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
- 绘制一条从
(startX, startY)到(stopX, stopY)的直线,使用指定的Paint。
drawRect(float left, float top, float right, float bottom, Paint paint)
- 绘制一个矩形,左上角为
(left, top),右下角为(right, bottom),使用指定的Paint。
drawCircle(float cx, float cy, float radius, Paint paint)
- 绘制一个圆形,圆心为
(cx, cy),半径为radius,使用指定的Paint。 canvas.drawCircle(300f, 300f, 200f, paint)
drawOval(float left, float top, float right, float bottom, Paint paint)
- 绘制一个椭圆,矩形区域由
(left, top)和(right, bottom)定义,使用指定的Paint。
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
- 绘制一个圆弧,
oval定义圆弧所在的矩形,startAngle是起始角度,sweepAngle是弧度,useCenter是否连接圆心。
drawRoundRect(RectF rect, float rx, float ry, Paint paint)
- 绘制一个圆角矩形,
rect定义矩形,rx和ry为水平和垂直方向的圆角半径。
2. 绘制路径
drawPath(Path path, Paint paint)
- 绘制路径
path,使用指定的Paint来设置样式。
drawText(String text, float x, float y, Paint paint)
- 在
(x, y)位置绘制文本text,使用指定的Paint。
drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
- 将文本
text绘制到Path路径上,hOffset和vOffset为水平和垂直偏移,paint用于设置文本样式。
3. 绘制位图
drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
- 绘制一个
Bitmap图像,图像的左上角放置在(left, top),使用指定的Paint。
drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
- 将
Bitmap中的源区域src绘制到目标区域dst,使用指定的Paint。
4. 画布操作
save()
- 保存当前画布的状态,通常与
restore()配对使用,用于保存和恢复画布状态(如变换矩阵、裁剪区域等)。
restore()
- 恢复上一个
save()保存的画布状态。
translate(float dx, float dy)
- 平移画布原点,移动
(dx, dy),影响后续所有绘制。
rotate(float degrees)
- 绕画布原点旋转
degrees度,影响后续所有绘制。
scale(float sx, float sy)
- 按
sx和sy缩放画布,影响后续所有绘制。
skew(float skewX, float skewY)
- 将画布沿水平方向和垂直方向倾斜
skewX和skewY,影响后续所有绘制。
5. 裁剪区域
clipRect(RectF rect)
- 将绘制区域裁剪为指定的矩形区域
rect,只有在这个区域内的绘制操作才会显示。
clipPath(Path path)
- 将绘制区域裁剪为指定的路径
path,只有在该路径内的绘制操作才会显示。
clipOutPath(Path path)
- 从当前的裁剪区域中去除
Path所定义的区域,后续的绘制将不影响此区域,通常用于创建具有空洞的形状。
6. 填充画布
drawColor(int color)
- 用指定的颜色填充整个画布,覆盖掉当前画布上的内容。
7. 代码
package com.s.testapp.view
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import com.s.testapp.R
class CustomDrawView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint: Paint = Paint().apply {
color = Color.RED
strokeWidth = 5f
style = Paint.Style.STROKE
}
// 绘制方法标识常量
companion object {
const val DRAW_LINE = 0
const val DRAW_RECT = 1
const val DRAW_CIRCLE = 2
const val DRAW_OVAL = 3
const val DRAW_ARC = 4
const val DRAW_ROUND_RECT = 5
const val DRAW_PATH = 6
const val DRAW_TEXT = 7
const val DRAW_BITMAP = 8
const val DRAW_COLOR = 9
const val CLIP_RECT = 10
const val CLIP_PATH = 11
const val CLIP_REGION = 12
const val TRANSLATE = 13
const val ROTATE = 14
const val SCALE = 15
const val SKEW = 16
const val SAVE_RESTORE = 17
}
private var drawMethod = DRAW_LINE // 默认绘制直线
// 设置绘制方法
fun setDrawMethod(method: Int) {
drawMethod = method
invalidate() // 强制重新绘制
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
when (drawMethod) {
DRAW_LINE -> drawLine(canvas)
DRAW_RECT -> drawRect(canvas)
DRAW_CIRCLE -> drawCircle(canvas)
DRAW_OVAL -> drawOval(canvas)
DRAW_ARC -> drawArc(canvas)
DRAW_ROUND_RECT -> drawRoundRect(canvas)
DRAW_PATH -> drawPath(canvas)
DRAW_TEXT -> drawText(canvas)
DRAW_BITMAP -> drawBitmap(canvas)
DRAW_COLOR -> drawColor(canvas)
CLIP_RECT -> clipRect(canvas)
CLIP_PATH -> clipPath(canvas)
TRANSLATE -> translate(canvas)
ROTATE -> rotate(canvas)
SCALE -> scale(canvas)
SKEW -> skew(canvas)
SAVE_RESTORE -> saveAndRestore(canvas)
}
}
// 绘制直线
private fun drawLine(canvas: Canvas) {
canvas.drawLine(100f, 100f, 500f, 500f, paint)
}
// 绘制矩形
private fun drawRect(canvas: Canvas) {
canvas.drawRect(100f, 100f, 500f, 500f, paint)
}
// 绘制圆形
private fun drawCircle(canvas: Canvas) {
canvas.drawCircle(300f, 300f, 200f, paint)
}
// 绘制椭圆
private fun drawOval(canvas: Canvas) {
canvas.drawOval(100f, 100f, 500f, 300f, paint)
}
// 绘制圆弧
private fun drawArc(canvas: Canvas) {
val oval = RectF(100f, 100f, 400f, 400f)
canvas.drawArc(oval, 0f, 90f, false, paint)
}
// 绘制圆角矩形
private fun drawRoundRect(canvas: Canvas) {
val rect = RectF(100f, 100f, 400f, 300f)
canvas.drawRoundRect(rect, 50f, 50f, paint)
}
// 绘制路径
private fun drawPath(canvas: Canvas) {
val path = Path().apply {
moveTo(100f, 100f)
lineTo(500f, 500f)
lineTo(100f, 500f)
close()
}
canvas.drawPath(path, paint)
}
// 绘制文本
private fun drawText(canvas: Canvas) {
paint.textSize = 60f
canvas.drawText("Hello, Canvas!", 100f, 300f, paint)
}
// 绘制位图
private fun drawBitmap(canvas: Canvas) {
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_launcher_background) // 请替换为实际的图片资源
canvas.drawBitmap(bitmap, 50f, 50f, paint)
}
// 绘制填充颜色
private fun drawColor(canvas: Canvas) {
canvas.drawColor(Color.LTGRAY) // 使用浅灰色填充整个画布
}
// 剪裁矩形区域
private fun clipRect(canvas: Canvas) {
val rect = Rect(100, 100, 400, 400)
canvas.clipRect(rect)
val paint: Paint = Paint().apply {
color = Color.RED
strokeWidth = 5f
style = Paint.Style.FILL
}
canvas.drawRect(50f, 50f, 500f, 500f, paint) // 只有剪裁区域内的部分会被绘制
}
// 剪裁路径区域
private fun clipPath(canvas: Canvas) {
val path = Path().apply {
addCircle(300f, 300f, 200f, Path.Direction.CW)
}
val paint: Paint = Paint().apply {
color = Color.RED
strokeWidth = 5f
style = Paint.Style.FILL
}
canvas.clipPath(path)
canvas.drawRect(50f, 50f, 500f, 500f, paint) // 只有剪裁区域内的部分会被绘制
}
// 平移
private fun translate(canvas: Canvas) {
canvas.save() // 保存当前画布状态
canvas.translate(200f, 200f) // 将画布原点平移
canvas.drawRect(100f, 100f, 500f, 500f, paint)
canvas.restore() // 恢复到原始状态
}
// 旋转
private fun rotate(canvas: Canvas) {
canvas.save()
canvas.rotate(45f, 300f, 300f) // 以画布中心为旋转点旋转45度
canvas.drawRect(100f, 100f, 500f, 500f, paint)
canvas.restore()
}
// 缩放
private fun scale(canvas: Canvas) {
canvas.save()
canvas.scale(1.5f, 1.5f, 300f, 300f) // 在画布中心缩放1.5倍
canvas.drawRect(100f, 100f, 500f, 500f, paint)
canvas.restore()
}
// 错切
private fun skew(canvas: Canvas) {
canvas.save()
canvas.skew(0.5f, 0f) // 水平错切
canvas.drawRect(100f, 100f, 500f, 500f, paint)
canvas.restore()
}
// Save and restore
private fun saveAndRestore(canvas: Canvas) {
// 裁剪区域
var path = Path().apply {
addRect(100f, 100f, 500f, 500f, Path.Direction.CW)
}
canvas.clipPath(path)
// 保存当前状态
canvas.save()
// 裁剪区域
path = Path().apply {
addRect(200f, 100f, 600f, 500f, Path.Direction.CW)
}
canvas.clipPath(path)
// 在裁剪区域内绘制内容
canvas.drawColor(Color.RED)
// 恢复到未裁剪状态
canvas.restore()
// 在整个画布上绘制其他内容
canvas.drawColor(Color.parseColor("#33090909"))
}
}