I have a logic that the user, when he touches a composable, a circle is drawn at that position that the user has pressed (it also supports more than one finger at a time) and if the user drags his finger, this circle follows the user’s finger
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.clip(RoundedCornerShape(16.dp))
.background(SoftPeach)
) {
Canvas(
modifier = Modifier
.fillMaxSize()
.pointerInput(state.isUserSelected) {
awaitEachGesture {
awaitFirstDown(requireUnconsumed = false)
do {
val touchedPositions = mutableMapOf <Long, Pair<Float, Float>>()
val actualIds = mutableSetOf < Long > ()
val event = awaitPointerEvent()
if (event.type == PointerEventType.Press ||
event.type == PointerEventType.Move
) {
event.changes.forEach { pointer - >
val id = pointer.id.value - 1
val x = pointer.position.x
val y = pointer.position.y
touchedPositions[id] = Pair(x, y)
actualIds.add(id)
}
viewModel.updateTouchedPositions(touchedPositions)
}
if (event.type == PointerEventType.Release) {
event.changes
.filter {
it.pressed
}
.forEach { pointer - >
actualIds.add(pointer.id.value - 1)
}
viewModel.updateTouchedPositions(touchedPositions)
}
} while (!event.changes.fastAny {
it.isConsumed
} &&
!viewModel.state.isUserSelected)
}
},
onDraw = {
if (state.isUserSelected.not() && state.touchedPositions.isNotEmpty()) {
state.touchedPositions.forEach { (id, position) - >
drawCircle(
color = Color.Red,
radius = 40. dp.toPx(),
center = Offset(position.first, position.second)
)
}
} else {
state.touchedPositions.forEach { (_, position) - >
drawCircle(
color = DarkPink,
radius = size.toPx(),
center = Offset(position.first, position.second)
)
drawCircle(
color = Color.White,
radius = 40. dp.toPx(),
center = Offset(position.first, position.second)
)
}
}
})
}
The method called updateTouchPositions is as follows:
fun updateTouchedPositions(
touchedPositions: Map < Long, Pair < Float, Float >>
) {
state = state.copy(
touchedPositions = touchedPositions
)
}
And on the other hand, to get a selected user effect, I have a LaunchedEffect that is as follows:
LaunchedEffect(key1 = state.touchedPositions.size) {
if (state.touchedPositions.size >= 2) {
(0. .5).reversed().forEach { countdown - >
delay(1000)
viewModel.setTimeRemaining(countdown)
}
val selectedUser = state.touchedPositions.entries
.random()
.key
viewModel.updateTouchedPositions(
state.touchedPositions.filter {
it.key == selectedUser
})
viewModel.isUserWinnerSelected(value = true)
viewModel.setIfIsColorExpanded(value = true)
viewModel.setTimeRemaining(value = 0)
}
}
Following this question where I already migrated to pointerInput I have two problems:
-
The first problem I find is that the displacement of the circle when the user makes the move input is not fluid, I imagine because he has to make too many operations.
-
The other more than a problem is that I haven’t found a way and I don’t know if it’s the right thing to do. When the LaunchedEffect is executed and the if is true, one of the circles is randomly selected and the value of isUserWinnerSelected changes to true, how can I, if this value is true, stop the loop that originates in the awaitEachGesture? I mean, I want to cancel it and stop it from listening for events until I manually tell it to do it again.