https://kotlinlang.org logo
Title
p

Pablichjenkov

02/28/2023, 5:17 AM
How to handle touch event interaction communication between parent/child LayoutNode. I have a Box within a HorizontalPager, the Box consume the touch event stream, something like:
var pointerEnabled by remember(someState) { mutableStateOf(true) }

Box(
            modifier = Modifier
                .fillMaxSize()
                .pointerInputModifierSwitcher(pointerEnabled, topBarState) {
                    detectDragGestures(
                        onDragStart = { offset ->
                            if (offset.x > 50) {
                                pointerEnabled = false
                            }
                            startX = offset.x
                        },
                        ...
                 }
)

// where pointerInput is added/removed from the modifiers depending on `pointerEnabled`
fun Modifier.pointerInputModifierSwitcher(
    enable: Boolean,
    key1: Any?,
    block: suspend PointerInputScope.() -> Unit
): Modifier {
    return if (enable) {
        this.pointerInput(key1, block)
    } else {
        this
    }
}
Now, If the drag gesture starts with offset.x > 50, I don’t want the Box to consume it but the HorizontalPager to do so. I set pointerEnabled to false so it triggers recomposition and the pointerInput is removed in the next recomposition. The problem is that once the drag gesture starts with offset.x > 50, when I cancel/remove the pointerInput modifier from the Box, the events are not propagated to the Pager until the drag gesture finishes. It is until the next drag gesture that the Pager gets to handle the touch events. What is the way to switch/divert the touch events to the parent (in this case the Pager), in the same gesture that is taking place. In the classic system there used to be an interceptTouch in the ViewGroup class that you could override, or also returning false from the onTouchEvent() would let the parent know you are not interested in handling the touch stream. Any similar in compose?
I found the
nestedScroll(nestedScrollConnection)
but the problem is, it is intended to be added as a modifier on the parent of the LayoutNode in question, in this case the Pager. But if I add the
nestedScroll()
modifier in one of the Pager's children, the callbacks don't get called. So it doesn't help me.
e

ephemient

03/05/2023, 5:19 AM
I'm not really sure what your issue is or what you're trying to accomplish
but you can "filter" from
pointerInput
, by using
awaitPointerEvent(pass = PointerEventPass.Initial)
and altering or consuming the event before continuing
p

Pablichjenkov

03/07/2023, 2:36 AM
Hi, sorry for my late response. I saw your reply guys but could not respond. What I want is simple, in the picture below, if the user start dragging or horizontal scrolling from within the red area, I want to consume the events and handle them as a predictive back. But if the user start scrolling in the green area I don’t want to consume the events at all.
I tried with:
.pointerInput(key1, block) {
    detectDragGestures(...)
}
and it works ok, in the sense that I always get the events stream and can do what I want. But, when the user starts dragging in the green area, I haven’t found a way to stop consuming the event stream and let the parent consume it instead. If I remove the pointer input from MyDraggableScreen after detecting the first touch in the green area, the parent does not get the event stream. The system waits until the current drag gesture finishes, then the next one is dispatched in the parent.
I haven’t tried
awaitPointerEvent
, I let you know it it makes the trick
Thanks for your feedback @ephemient. I was able to achieve what I wanted using something similar to the code bellow. This time Google search did not sent me to the right documentation page. The Android guide talks about the other gestures but not much on this awaitPointerEventScope thing.
.pointerInput(topBarState) {
            forEachGesture {
                        awaitPointerEventScope {

                            val eventDown = awaitFirstDown(requireUnconsumed = true)
                           
                            if (eventDown.position.x < PredictiveArea) {
                                eventDown.consume()
                                startX.value = eventDown.position.x

                                do {

                                    val event: PointerEvent = awaitPointerEvent(PointerEventPass.Main)
                                   
                                    event.changes.forEach {
                                        it.consume()
                                    }

                                    event.changes.lastOrNull()?.let {
                                        deltaX.value = it.position.x -startX.value
                                    }

                                   
                                } while (event.changes.any { it.pressed })

                                if (deltaX.value > PredictiveArea) {
                                    stackState.handleBackPress()
                                }
                                startX.value = Float.MAX_VALUE
                                deltaX.value = 0f
                            }
                        }
                    }
                }