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
相关产品推荐
相关产品推荐

