I am trying to draw straight lines using canvas and the detectDragGestures
method, but so far I have only achieved chaotic behavior without any fixations. I am seeking a solution to ensure that my lines are always straight.
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun CanvasAction() {
var centerOffset by remember {
mutableStateOf(Offset.Zero)
}
var points by remember {
mutableStateOf<List<Point>>(listOf())
}
Canvas(
modifier = Modifier
.fillMaxSize()
.pointerInput(key1 = Unit) {
detectDragGestures(
onDragStart = { offset ->
points = points + Point(offset = offset, isStartedPosition = true)
},
onDrag = { change, offset ->
change.historical.forEach {
centerOffset = it.position
}
points = points + change.historical.map {
Point(offset = it.position, isStartedPosition = false)
}
},
)
},
) {
drawCircle(
color = Color.Blue,
radius = 7.dp.toPx(),
center = centerOffset,
)
val path = Path()
points.forEach { point ->
if (point.isStartedPosition) {
path.moveTo(point.offset.x, point.offset.y)
} else {
path.lineTo(point.offset.x, point.offset.y)
}
}
drawPath(
path = path,
color = Color.Blue,
style = Stroke(width = 2.dp.toPx()),
)
}
}
The problem is that you save each single point during the drag operation.
If you instead only remember the starting point during onDragStart
and calculate the current point during onDrag
while ignoring any intermediate points, you can draw a path between those two points and always have a straight line.
If you want to repeat this for multiple drag gestures while keeping the previous lines you need to use onDragEnd
to save the current line.
It could look like this:
@Composable
fun CanvasAction() {
var centerOffset by remember {
mutableStateOf<Offset?>(null)
}
var finishedLines by remember {
mutableStateOf<List<Rect>>(emptyList())
}
var currentLine by remember {
mutableStateOf<Rect?>(null)
}
Canvas(
modifier = Modifier
.fillMaxSize()
.pointerInput(key1 = Unit) {
detectDragGestures(
onDragStart = { offset ->
centerOffset = offset
currentLine = Rect(offset, offset)
},
onDrag = { _, dragAmount ->
centerOffset = centerOffset?.plus(dragAmount)
currentLine = currentLine?.let {
it.copy(
right = it.right + dragAmount.x,
bottom = it.bottom + dragAmount.y,
)
}
},
onDragEnd = {
centerOffset = null
currentLine?.let { finishedLines += it }
currentLine = null
},
onDragCancel = {
centerOffset = null
currentLine = null
},
)
},
) {
centerOffset?.let {
drawCircle(
color = Color.Blue,
radius = 7.dp.toPx(),
center = it,
)
}
val linesToDraw = currentLine?.let { finishedLines + it } ?: finishedLines
val path = Path()
linesToDraw.forEach {
path.moveTo(it.left, it.top)
path.lineTo(it.right, it.bottom)
}
drawPath(
path = path,
color = Color.Blue,
style = Stroke(width = 2.dp.toPx()),
)
}
}
To represent a line I have used an androidx.compose.ui.geometry.Rect
that has two offset coordinates that I use to save the starting point and the ending point of the line; the Point
class isn't used any more. finishedLines
is a list of all lines that were already drawn, where currentLine
is the line that, well, currently is drawn.
The other changes mostly regard null handling when there currently is nothing being drawn.