Hello, I’d like to enable multi finger gesture on ...
# compose
n
Hello, I’d like to enable multi finger gesture on a line chart but I can’t somehow manage to show multiple circle using Compose. What do I need to do exactly to achieve it? Sample code in thread 🧵
Copy code
@Composable
fun LineChart(points: List<Float>, modifier: Modifier = Modifier) {
    val touchPoint = remember { mutableStateListOf<Offset>() }

    Canvas(
        modifier = modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                detectTransformGestures { centroid, pan, _, _ ->

                    touchPoint += centroid
                }
            }
    ) {
        val width = size.width
        val height = size.height

        // Calculate the step for x axis
        val stepX = width / (points.size - 1)

        // Calculate the step for y axis
        val maxY = points.maxOrNull() ?: 1f
        val stepY = height / maxY

        // Draw line connecting points
        points.forEachIndexed { index, point ->
            val x = index * stepX
            val y = height - point * stepY
            if (index < points.size - 1) {
                val nextX = (index + 1) * stepX
                val nextY = height - points[index + 1] * stepY
                drawLine(start = Offset(x, y), end = Offset(nextX, nextY), color = Color.Blue, strokeWidth = 3f)
            }
            // Draw points
            drawCircle(color = Color.Blue, center = Offset(x, y), radius = 6f)
        }

        touchPoint.forEach {
            drawCircle(color = Color.Red, center = it, radius = 30f)
        }
    }
}
Screen_recording_20240509_023535.mp4
I want to draw the identical things using my both finger but the red line blends in one
r
You are using detectTransformGestures which combines the touch events and gives you the centroid which is why its in the middle between the two. If you switch to using pointerInput / awaitPointerEventScope, you'll get each individual pointer on screen, you can then store a list of the pointers and draw each individually.
The following snippet gets all the pointer events and stores them, and draws all of them at once on screen, you probably want to store the latest values and only display those, but note the
event.changes.forEach
will give you all the pointer events at present.
Copy code
@Composable
@Preview
fun Multitouch() {
    var listPointers = remember { mutableStateListOf<Offset>() }
    Box(
        Modifier
            .size(400.dp)
            //.background(Color.Blue)
            .pointerInput(Unit) {
                awaitPointerEventScope {
                    while (true) {
                        val event = awaitPointerEvent()
                        event.changes.forEach {
                            listPointers.add(it.position)
                        }
                    }
                }
            }
            .drawBehind {
                listPointers.forEach {
                    drawCircle(Color.Red, 50f, it)
                }
            }.fillMaxSize()
    )
}
n
Thank you @Rebecca Franks allo love, works like a charm I added these two code in order to limit the touch size to 2 and remove all circles upon release
Copy code
if (listPointers.size > 2) listPointers.removeAt(0)
if (event.type == PointerEventType.Release) listPointers.clear()
Do you think there is a better approach in order to restrict touch size to any number like 2 and vanish all circles upon release?
Screen_recording_20240509_154636.mp4
👍 1
Hello! Follow up on this I have a custom gesture implementation for multi finger touch but there is a strange issue with a rapid second finger touch on the component. I have below code to keep touch coordinates
Copy code
onDrag = { changes ->
    changes
        .fastForEach {
            touchCoordinates[it.id] = it.position.x
            if (it.changedToUp()) {
                touchCoordinates.remove(it.id)
            }
        }
},
However, when I touch the component very rapidly, Compose doesn’t return
isConsumed = true
or
pressed = false
for the previous one that’s why I am failing to remove the event from my map. Is my code nonsense or is this Compose’s glitch?
r
I'm curious if its still happening if you change it to changedToUpIgnoreConsumed()? Or maybe its just being re-added again as it comes as another different event and you have added it again and again to the array. Otherwise I think you should file a bug 🤔
n
Sorry @Rebecca Franks I think the issue is on my side. I basically have a check like below
Copy code
val downPointerCount = event.changes.fastMap {
        it.pressed
    }.size

    val requirementFulfilled = downPointerCount <= numberOfPointers

    val canceled = event.changes.fastAny { it.isConsumed }

    if (!canceled && requirementFulfilled) {
        gestureStarted = true

        onDrag(event.changes)

        event.changes.fastForEach {
            if (it.positionChanged()) {
                it.consume()
            }
        }
    }
} while (!canceled && event.changes.fastAny { it.pressed })
Basically, when I rapidly touch the chart with my second finger
downPointerCount
is being
2
therefore I can’t invoke
onDrag
and failing to remove the previous item when I release my first finger and only keep the second one so I am seeing multiple black dots on the UI 🙂 Looks like I need a else to return a signal to my component in order to remove from map but do you know how can I do that?
Copy code
if (!canceled && requirementFulfilled) {
   ...
} else {
   // what to return here
}
1