Android Canvas的基本操作

398 阅读8分钟

在 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
  • image.png

drawRect(float left, float top, float right, float bottom, Paint paint)

  • 绘制一个矩形,左上角为 (left, top),右下角为 (right, bottom),使用指定的 Paint
  • image.png

drawCircle(float cx, float cy, float radius, Paint paint)

  • 绘制一个圆形,圆心为 (cx, cy),半径为 radius,使用指定的 Paint
  • canvas.drawCircle(300f, 300f, 200f, paint)
  • image.png

drawOval(float left, float top, float right, float bottom, Paint paint)

  • 绘制一个椭圆,矩形区域由 (left, top)(right, bottom) 定义,使用指定的 Paint
  • image.png

drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

  • 绘制一个圆弧,oval 定义圆弧所在的矩形,startAngle 是起始角度,sweepAngle 是弧度,useCenter 是否连接圆心。
  • image.png

drawRoundRect(RectF rect, float rx, float ry, Paint paint)

  • 绘制一个圆角矩形,rect 定义矩形,rxry 为水平和垂直方向的圆角半径。
  • image.png

2. 绘制路径

drawPath(Path path, Paint paint)

  • 绘制路径 path,使用指定的 Paint 来设置样式。
  • image.png

drawText(String text, float x, float y, Paint paint)

  • (x, y) 位置绘制文本 text,使用指定的 Paint
  • image.png

drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)

  • 将文本 text 绘制到 Path 路径上,hOffsetvOffset 为水平和垂直偏移,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() 保存的画布状态。
  • image.png

translate(float dx, float dy)

  • 平移画布原点,移动 (dx, dy),影响后续所有绘制。

rotate(float degrees)

  • 绕画布原点旋转 degrees 度,影响后续所有绘制。

scale(float sx, float sy)

  • sxsy 缩放画布,影响后续所有绘制。

skew(float skewX, float skewY)

  • 将画布沿水平方向和垂直方向倾斜 skewXskewY,影响后续所有绘制。

5. 裁剪区域

clipRect(RectF rect)

  • 将绘制区域裁剪为指定的矩形区域 rect,只有在这个区域内的绘制操作才会显示。
  • image.png

clipPath(Path path)

  • 将绘制区域裁剪为指定的路径 path,只有在该路径内的绘制操作才会显示。
  • image.png

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"))
    }

}