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

Android(Kotlin) Canvas绘图撤销异常:仅SmoothLine功能正常

绘图撤销功能修复方案

问题根源

你的撤销逻辑依赖stackPaths栈存储所有绘制路径,restoreStacks方法会清空extraBitmap后重绘栈中所有Path。但除了SmoothLine外,其他图形都是直接调用drawLine/drawRect等API将图形画在extraCanvas上,没有把图形转化为Path存入撤销栈。这导致撤销时栈中没有这些图形的记录,restoreStacks无法清除已经画在extraBitmap上的内容,只有新绘制内容覆盖时才会消失。

修复步骤

1. 统一所有图形的绘制逻辑:将图形转化为Path存入撤销栈

所有图形在完成绘制(ACTION_UP)时,都要把当前图形转化为对应的Path,调用addToStack存入栈,再通过Path绘制到extraCanvas。

修改Line的触摸事件处理

private fun onTouchEventLine(event: MotionEvent){
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            currentX = motionTouchEventX
            currentY = motionTouchEventY
            path.reset()
            path.moveTo(motionTouchEventX, motionTouchEventY)
        }
        MotionEvent.ACTION_MOVE -> invalidate()
        MotionEvent.ACTION_UP -> {
            // 用Path构建直线
            path.reset()
            path.moveTo(currentX, currentY)
            path.lineTo(motionTouchEventX, motionTouchEventY)
            // 存入栈并绘制到extraCanvas
            addToStack(path)
            extraCanvas.drawPath(path, paint)
            path.reset()
        }
    }
}

修改Rectangle的触摸事件处理

private fun onTouchEventRectangle(event: MotionEvent){
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            currentX = motionTouchEventX
            currentY = motionTouchEventY
            invalidate()
        }
        MotionEvent.ACTION_MOVE -> invalidate()
        MotionEvent.ACTION_UP -> {
            // 用Path构建矩形
            path.reset()
            val right = if (currentX > motionTouchEventX) currentX else motionTouchEventX
            val left = if (currentX > motionTouchEventX) motionTouchEventX else currentX
            val bottom = if (currentY > motionTouchEventY) currentY else motionTouchEventY
            val top = if (currentY > motionTouchEventY) motionTouchEventY else currentY
            path.addRect(left, top, right, bottom, Path.Direction.CW)
            // 存入栈并绘制到extraCanvas
            addToStack(path)
            extraCanvas.drawPath(path, paint)
            path.reset()
            invalidate()
        }
    }
}

修改Circle的触摸事件处理

private fun onTouchEventCircle(event: MotionEvent){
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            currentX = motionTouchEventX
            currentY = motionTouchEventY
            invalidate()
        }
        MotionEvent.ACTION_MOVE -> invalidate()
        MotionEvent.ACTION_UP -> {
            val radius = calculateRadius(currentX, currentY, motionTouchEventX, motionTouchEventY)
            // 用Path构建圆形
            path.reset()
            path.addCircle(currentX, currentY, radius, Path.Direction.CW)
            // 存入栈并绘制到extraCanvas
            addToStack(path)
            extraCanvas.drawPath(path, paint)
            path.reset()
            invalidate()
        }
    }
}

修改Square的触摸事件处理

Square复用Rectangle的逻辑,只需在ACTION_UP时构建矩形Path:

private fun onTouchEventSquare(event: MotionEvent){
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            currentX = motionTouchEventX
            currentY = motionTouchEventY
            invalidate()
        }
        MotionEvent.ACTION_MOVE -> {
            adjustSquare(motionTouchEventX, motionTouchEventY)
            invalidate()
        }
        MotionEvent.ACTION_UP -> {
            adjustSquare(motionTouchEventX, motionTouchEventY)
            // 用Path构建正方形(本质是等宽高的矩形)
            path.reset()
            val right = if (currentX > motionTouchEventX) currentX else motionTouchEventX
            val left = if (currentX > motionTouchEventX) motionTouchEventX else currentX
            val bottom = if (currentY > motionTouchEventY) currentY else motionTouchEventY
            val top = if (currentY > motionTouchEventY) motionTouchEventY else currentY
            path.addRect(left, top, right, bottom, Path.Direction.CW)
            // 存入栈并绘制到extraCanvas
            addToStack(path)
            extraCanvas.drawPath(path, paint)
            path.reset()
            invalidate()
        }
    }
}

修改Triangle的触摸事件处理

Triangle需要在完成三条线绘制后,将整个三角形的Path存入栈:

private fun onTouchEventTriangle(event: MotionEvent) {
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            countTouch++
            if (countTouch == 1) {
                currentX = motionTouchEventX
                currentY = motionTouchEventY
                path.reset()
                path.moveTo(currentX, currentY)
            } else if (countTouch == 2) {
                path.lineTo(motionTouchEventX, motionTouchEventY)
                basexTriangle = motionTouchEventX
                baseyTriangle = motionTouchEventY
            }
        }
        MotionEvent.ACTION_MOVE -> if(countTouch != 3) invalidate()
        MotionEvent.ACTION_UP -> {
            countTouch++
            if (countTouch == 2) {
                extraCanvas.drawPath(path, paint)
            } else if (countTouch >= 3) {
                // 闭合三角形Path
                path.lineTo(motionTouchEventX, motionTouchEventY)
                path.lineTo(currentX, currentY)
                path.close()
                // 存入栈并绘制到extraCanvas
                addToStack(path)
                extraCanvas.drawPath(path, paint)
                countTouch = 0
                path.reset()
            }
            invalidate()
        }
    }
}

2. 修复restoreStacks方法的背景清除逻辑

之前用extraBitmap.eraseColor(Color.TRANSPARENT)会把背景擦成透明,应该用预设的背景色:

private fun restoreStacks(){
    extraBitmap.eraseColor(backgroundColor) // 改为背景色,而不是透明
    stackPaths.forEachIndexed { index, path ->
        extraCanvas.drawPath(path, stackPaint[index])
    }
    invalidate()
}

修复后效果

所有图形完成绘制后都会被转化为Path存入撤销栈,调用undoLastAction时,栈顶元素被移除,restoreStacks会清空画布并重绘剩余的所有Path,实现和SmoothLine一致的撤销效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 08:14:56