Edit
It's been a while since i posted this answer and as i got feedback from this question that previous answer was a little bit confusing for beginners, so i simplify it, library for this gesture and more is available in github repo.
We need motion states as we have with View's first
enum class MotionEvent {
Idle, Down, Move, Up
}
Idle state is needed to not leave state on Up because if any recomposition happens your Canvas gets recomposed with Up state which leads to unwanted drawings or even crashes.
Path, current touch position and touch states
var motionEvent by remember { mutableStateOf(MotionEvent.Idle) }
// This is our motion event we get from touch motion
var currentPosition by remember { mutableStateOf(Offset.Unspecified) }
// This is previous motion event before next touch is saved into this current position
var previousPosition by remember { mutableStateOf(Offset.Unspecified) }
previousPosition
is optional i use it because i want to draw smooth lines with path.quadraticBezierTo
, instead of path.lineTo while moving with pointer
Modifier for creating touch events. Modifier.clipToBounds()
is to prevent drawing outside of Canvas.
val drawModifier = Modifier
.fillMaxWidth()
.height(300.dp)
.clipToBounds()
.background(Color.White)
.pointerMotionEvents(
onDown = { pointerInputChange: PointerInputChange ->
currentPosition = pointerInputChange.position
motionEvent = MotionEvent.Down
pointerInputChange.consume()
},
onMove = { pointerInputChange: PointerInputChange ->
currentPosition = pointerInputChange.position
motionEvent = MotionEvent.Move
pointerInputChange.consume()
},
onUp = { pointerInputChange: PointerInputChange ->
motionEvent = MotionEvent.Up
pointerInputChange.consume()
},
delayAfterDownInMillis = 25L
)
Modifier.pointerMotionEvents
custom gesture library i wrote for it be counterpart of onTouchEvent, it's available on github repo above, and here is a detailed explanation about gestures, you can easily build your own gesture if you don't want to. Delay after first touch occurs on onTouchEvent of View has, it's about 16ms on my devices, this is the fastest i measured, i added to gestures on Compose too because Canvas can't process down events when user has a very swift pointer movement initially.
And apply this modifier to canvas and move or draw based on current state and position
Canvas(modifier = drawModifier) {
when (motionEvent) {
MotionEvent.Down -> {
path.moveTo(currentPosition.x, currentPosition.y)
previousPosition = currentPosition
}
MotionEvent.Move -> {
path.quadraticBezierTo(
previousPosition.x,
previousPosition.y,
(previousPosition.x + currentPosition.x) / 2,
(previousPosition.y + currentPosition.y) / 2
)
previousPosition = currentPosition
}
MotionEvent.Up -> {
path.lineTo(currentPosition.x, currentPosition.y)
currentPosition = Offset.Unspecified
previousPosition = currentPosition
motionEvent = MotionEvent.Idle
}
else -> Unit
}
drawPath(
color = Color.Red,
path = path,
style = Stroke(width = 4.dp.toPx(), cap = StrokeCap.Round, join = StrokeJoin.Round)
)
}
Github repo for a full drawing app also available here.