0

We have pointerInput for detecting tap, drag and pan events, and it also provides a handy awaitPointerEventScope, the pointer being the finger, for mobile devices here. Now, we do have a awaitFirstDown() for detecting when the finger first makes contact with the screen, but I can't seem to find an upper equivalent to this method.

I have a little widget that I wish to detect taps on, but the thing is that the app is made for such a use-case that the user might be in weird positions during its use, and so I wished to have it trigger the required action on just a touch and lift of the finger. The paranoia is that the user might accidentally 'drag' their finger (even by a millimeter, android still picks it up), and I do not want that to be the case. I could implement a tap as well as a drag listener, but none of them offer a finger-lift detection, as far as I know.

What solution, if there is one as of now, is suitable for the use-case while adhering to and leveraging the declarative nature of Compose while keeping the codebase to a minimum?

Richard Onslow Roper
  • 5,477
  • 2
  • 11
  • 42

2 Answers2

1

pointerInteropFilter is the way to go

Item(

   Modifier.pointerInteropFilter {

      if (it.action == MotionEvent.ACTION_UP) {
        triggerAction()
      }

      true // Consume touch, return false if consumption is not required here

   }

)
Richard Onslow Roper
  • 5,477
  • 2
  • 11
  • 42
1

Better way, and what is suggested by Android code if you are not using interoperability with existing View code is Modifier.pointerInput()

A special PointerInputModifier that provides access to the underlying MotionEvents originally dispatched to Compose. Prefer pointerInput and use this only for interoperation with existing code that consumes MotionEvents. While the main intent of this Modifier is to allow arbitrary code to access the original MotionEvent dispatched to Compose, for completeness, analogs are provided to allow arbitrary code to interact with the system as if it were an Android View.

val pointerModifier = Modifier
    .pointerInput(Unit) {
        forEachGesture {

            awaitPointerEventScope {
                
                awaitFirstDown()
               // ACTION_DOWN here
               
                do {
                    
                    //This PointerEvent contains details including
                    // event, id, position and more
                    val event: PointerEvent = awaitPointerEvent()
                    // ACTION_MOVE loop

                    // Consuming event prevents other gestures or scroll to intercept
                    event.changes.forEach { pointerInputChange: PointerInputChange ->
                        pointerInputChange.consumePositionChange()
                    }
                } while (event.changes.any { it.pressed })

                // ACTION_UP is here
            }
        }
}

This answer explains in detail how it works, internals and key points to consider when creating your own gestures.

Also this is a gesture library you can check out for onTouchEvent counterpart and 2 for detectTransformGestures with onGestureEnd callback and returns number of pointers down or list of PointerInputChange in onGesture event. Which can be used as

 Modifier.pointerMotionEvents(
    onDown = {
        // When down is consumed
        it.consumeDownChange()
    },
    onMove = {
        // Consuming move prevents scroll other events to not get this move event
        it.consumePositionChange()
    },
    onUp= {}
    delayAfterDownInMillis = 20
)

Edit

As of 1.2.0-beta01, partial consumes like PointerInputChange.consemePositionChange(), PointerInputChange.consumeDownChange(), and one for consuming all changes PointerInputChange.consumeAllChanges() are deprecated

PointerInputChange.consume()

is the only one to be used preventing other gestures/event.

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Thank you for the resources, @Thracian. If i do not implement a drag/scroll at all, I do not need to consume the position changes, right? – Richard Onslow Roper May 12 '22 at 13:16
  • @MARSK consuming changes doesn't mean anything other than preventing other gestures using them. Drag and every event uses them. Sometimes when you build custom views with multiple gestures in some conditions you might consume them like for instance a drawing app. You might want to consume touch events if only pointer is down to draw but when 2 or more are down you don't consume them so you can rotate or translate with 2 fingers with ponterInput with detectGestures. – Thracian May 12 '22 at 13:21
  • Consuming is like returning true from onTouchEvent but touch api of Compose is way simpler. At the beginning it looks quite scary but when you get the hang of it you understand and implement propagations faster. I have a tutorial that covers gestures. It's in link to my answer with detail. – Thracian May 12 '22 at 13:22
  • Yes I saw the link to the tutorial, good sir. I'll try to take it in one sitting but thank you so much for taking the time to post such a detailed explanation. Highly appreciated. – Richard Onslow Roper May 12 '22 at 13:25