Undo/Redo 和 Canvas 位图绘图 (KOTLIN)

Undo/Redo with Canvas Bitmap drawing (KOTLIN)

所以我正在尝试 Undo/Redo 操作,在 Whosebug 上有一些关于这个问题的答案,但其中任何一个都没有帮助解决我的问题。因此,我有 canvas 实现的自定义视图,其中我有一个数组来存储我的绘图路径,但任何时候我开始在其中存储时什么都不做。任何建议或 link 表示赞赏。

我的自定义视图 class:

private const val STROKE_WIDTH = 12f

class CanvasCustomView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
    private var path = Path()

    private val paths = ArrayList<Path>()
    private val undonePaths = ArrayList<Path>()

    private lateinit var extraCanvas: Canvas
    private lateinit var extraBitmap: Bitmap

    private var motionTouchEventX = 0f
    private var motionTouchEventY = 0f

    private var currentX = 0f
    private var currentY = 0f

    private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop

    private fun touchStart() {
        path.reset()
        path.moveTo(motionTouchEventX, motionTouchEventY)
        currentX = motionTouchEventX
        currentY = motionTouchEventY
    }

    private fun touchMove() {
        val distanceX = abs(motionTouchEventX - currentX)
        val distanceY = abs(motionTouchEventY - currentY)

        if (distanceX >= touchTolerance || distanceY >= touchTolerance) {
            path.quadTo(
                currentX,
                currentY,
                (motionTouchEventX + currentX) / 2,
                (motionTouchEventY + currentY) / 2)
            currentX = motionTouchEventX
            currentY = motionTouchEventY
            extraCanvas.drawPath(path, paint)
        }
        invalidate()
    }

    private fun touchUp() {
        path.reset()
    }

    fun undoCanvasDrawing(){
// im trying to do undo here
    }

    fun clearCanvasDrawing() {
        extraCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
        path.reset()
        invalidate()
    }

    private val backgroundColor = ResourcesCompat.getColor(resources, R.color.colorBackground, null)
    private val drawColor = ResourcesCompat.getColor(resources, R.color.colorPaint, null)

    private val paint = Paint().apply {
        color = drawColor
        isAntiAlias = true
        isDither = true
        style = Paint.Style.STROKE
        strokeJoin = Paint.Join.ROUND
        strokeCap = Paint.Cap.ROUND
        strokeWidth = STROKE_WIDTH
    }

    override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
        super.onSizeChanged(width, height, oldWidth, oldHeight)

        if (::extraBitmap.isInitialized) extraBitmap.recycle()

        extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)

        extraCanvas = Canvas(extraBitmap)
        extraCanvas.drawColor(backgroundColor)
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawBitmap(extraBitmap, 0f, 0f, null)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (event == null)
            return false

        motionTouchEventX = event.x
        motionTouchEventY = event.y

        when (event.action) {
            MotionEvent.ACTION_DOWN -> touchStart()
            MotionEvent.ACTION_MOVE -> touchMove()
            MotionEvent.ACTION_UP -> touchUp()
        }
        return true
    }
}

所以我决定不使用位图,以防你需要存储一组路径,而且它非常昂贵。所以我的解决方案是 undo/redo 和重置功能

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import androidx.core.content.res.ResourcesCompat
import kotlin.math.abs

class CanvasCustomView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
    companion object {
        private const val STROKE_WIDTH = 12f
    }

    private var path = Path()

    private val paths = ArrayList<Path>()
    private val undonePaths = ArrayList<Path>()

    private val extraCanvas: Canvas? = null

    private var motionTouchEventX = 0f
    private var motionTouchEventY = 0f

    private var currentX = 0f
    private var currentY = 0f

    private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop

    private val paint = Paint().apply {
        color = ResourcesCompat.getColor(resources, R.color.colorBlack, null)
        isAntiAlias = true
        isDither = true
        style = Paint.Style.STROKE
        strokeJoin = Paint.Join.ROUND
        strokeCap = Paint.Cap.ROUND
        strokeWidth = STROKE_WIDTH
    }

    fun resetCanvasDrawing() {
        path.reset() // Avoiding saving redo from Path()
        paths.clear()
        invalidate()
    }

    fun undoCanvasDrawing() {
        if (paths.size > 0) {
            undonePaths.add(paths.removeAt(paths.size - 1))
            invalidate()
        } else {
            Log.d("UNDO_ERROR", "Something went wrong with UNDO action")
        }
    }

    fun redoCanvasDrawing() {
        if (undonePaths.size > 0) {
            paths.add(undonePaths.removeAt(undonePaths.size - 1))
            invalidate()
        } else {
            Log.d("REDO_ERROR", "Something went wrong with REDO action")
        }
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        for (Path in paths) {
            canvas?.drawPath(Path, paint)
        }
        canvas?.drawPath(path, paint)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (event == null)
            return false

        motionTouchEventX = event.x
        motionTouchEventY = event.y

        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                undonePaths.clear()
                path.reset()
                path.moveTo(motionTouchEventX, motionTouchEventY)
                currentX = motionTouchEventX
                currentY = motionTouchEventY
                invalidate()
            }

            MotionEvent.ACTION_MOVE -> {
                val distanceX = abs(motionTouchEventX - currentX)
                val distanceY = abs(motionTouchEventY - currentY)

                if (distanceX >= touchTolerance || distanceY >= touchTolerance) {
                    path.quadTo(
                        currentX,
                        currentY,
                        (motionTouchEventX + currentX) / 2,
                        (currentY + motionTouchEventY) / 2)
                    currentX = motionTouchEventX
                    currentY = motionTouchEventY
                }
                invalidate()
            }

            MotionEvent.ACTION_UP -> {
                path.lineTo(currentX, currentY)
                extraCanvas?.drawPath(path, paint)
                paths.add(path)
                path = Path()
            }
        }
        return true
    }
}