Reputation: 1165
So im trying to to Undo/Redo action and there is a few answers on stackoverflow about this problem, but any of them is not helping with my issue. So I have my custom view for canvas implementation, where I have an arrays to store paths of my drawing, but any time Im start storing in it just do nothing. Any advices or link are appreciated.
My custom view 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
}
}
Upvotes: 1
Views: 970
Reputation: 1165
So I decide to not use Bitmap in case you need to store a collection of Pathses,and it's very expensive. So there is my solution with undo/redo and reset functionality
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
}
}
Upvotes: 1