You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android自定义DrawingView代码工作原理解析求助

Android自定义DrawingView代码全解析

原始代码

class DrawingView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
    private lateinit var mDrawPath: FingerPath
    private lateinit var mCanvasBitmap: Bitmap
    private lateinit var mCanvasPaint: Paint
    private lateinit var mDrawPaint: Paint
    private var mBrushSize = 0
    private var color = Color.BLACK
    private lateinit var canvas: Canvas

    init {
        setUpDrawing()
    }

    private fun setUpDrawing() {
        mDrawPaint = Paint()
        mDrawPath = FingerPath(color, mBrushSize.toFloat())
        mDrawPaint.color = color
        mDrawPaint.style = Paint.Style.STROKE
        mDrawPaint.strokeJoin = Paint.Join.ROUND
        mDrawPaint.strokeCap = Paint.Cap.ROUND
        mCanvasPaint = Paint(Paint.DITHER_FLAG)
        mBrushSize = 20

    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mCanvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
        canvas = Canvas(mCanvasBitmap)

    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawBitmap(mCanvasBitmap, 0f, 0f, mDrawPaint)
        if (!mDrawPath.isEmpty) {
            mDrawPaint.strokeWidth = mDrawPath.brushThickness
            mDrawPaint.color = mDrawPath.color
            canvas.drawPath(mDrawPath, mDrawPaint)
        }
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        val touchX = event?.x
        val touchY = event?.y

        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                mDrawPath.color = color
                mDrawPath.brushThickness = mBrushSize.toFloat()

                mDrawPath.reset()
                mDrawPath.moveTo(touchX!!, touchY!!)
            }

            MotionEvent.ACTION_MOVE -> {
                mDrawPath.lineTo(touchX!!, touchY!!)

            }

            MotionEvent.ACTION_UP -> {
                mDrawPath = FingerPath(color, mBrushSize.toFloat())
            }
            else -> return false
        }

        invalidate()
        return true
    }

    internal inner class FingerPath(var color: Int, var brushThickness: Float) : Path()
}

用户问题

我正在学习Android开发课程,讲师正在开发一款绘图应用,但他未解释代码中变量的用途、重写函数的原因,导致我无法理解这段自定义DrawingView代码的工作机制,恳请帮忙解析其运行逻辑。


一、核心变量含义

  • mDrawPath: 继承自Android原生Path的自定义类实例,不仅记录手指绘制的轨迹路径,还携带当前路径的颜色、画笔粗细信息。
  • mCanvasBitmap: 离屏Bitmap,作为"绘制底图"保存所有已完成的绘制内容,避免每次重绘都重复绘制历史路径。
  • mCanvasPaint: 开启了DITHER_FLAG的画笔,用于优化Bitmap绘制时的颜色抖动效果,让色彩过渡更平滑。
  • mDrawPaint: 实际绘制路径的画笔,控制线条的颜色、粗细、端点/拐角样式。
  • mBrushSize: 画笔粗细的数值,默认设为20。
  • color: 当前画笔的颜色,默认黑色。
  • canvas: 绑定到mCanvasBitmap的画布对象,专门用来将完成的绘制内容持久化到底图上。

二、重写核心函数的作用

1. onSizeChanged

当View尺寸发生变化(首次加载、屏幕旋转等场景)时自动调用。这里用来创建与View尺寸完全匹配的mCanvasBitmap,并绑定对应的Canvas,确保绘制区域和View可视区域完全对齐。

2. onDraw

View的核心渲染方法,系统在需要刷新界面时自动调用:

  • 第一步绘制mCanvasBitmap,把历史绘制内容显示出来,保证之前画的线条不会丢失;
  • 第二步如果当前有未完成的绘制路径(手指还在画),就用mDrawPaint把这条实时路径画到View画布上,让用户看到正在绘制的线条。

3. onTouchEvent

处理用户触摸交互,是绘图功能的核心逻辑:

  • ACTION_DOWN: 手指按下时,重置当前路径,将路径起点定位到触摸坐标,同时同步当前的画笔颜色和粗细;
  • ACTION_MOVE: 手指移动时,不断给路径添加线段,记录完整的移动轨迹;
  • ACTION_UP: 手指抬起时,创建新的FingerPath实例,准备下一次绘制;
  • 每次处理完触摸事件后调用invalidate(),触发onDraw刷新界面,实时显示绘制效果。

三、整体运行流程

  1. 初始化阶段: View创建时执行init块,调用setUpDrawing初始化画笔、路径对象,设置默认的画笔样式(圆角端点/拐角、描边模式)、粗细和颜色。
  2. 尺寸确定: View尺寸确定后,onSizeChanged创建对应大小的底图Bitmap和绑定的Canvas。
  3. 触摸交互:
    • 手指按下:初始化当前绘制路径,记录起点;
    • 手指移动:扩展路径并触发界面刷新,实时显示当前线条;
    • 手指抬起:重置路径,准备下一次绘制;
  4. 渲染显示: 每次刷新时先绘制历史底图,再绘制当前未完成的路径,实现"实时绘制+历史保留"的效果。

代码缺陷说明

这段代码存在一个关键问题:手指抬起时,当前绘制的路径没有被画到mCanvasBitmap上,导致抬起后线条会消失。正确的做法是在ACTION_UP时将路径持久化到底图:

MotionEvent.ACTION_UP -> {
    canvas.drawPath(mDrawPath, mDrawPaint)
    mDrawPath = FingerPath(color, mBrushSize.toFloat())
}

内容的提问来源于stack exchange,提问作者Ionut

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 09:36:10