Search code examples
androidkotlinontouchlistener

Distinguish between short tap (click) and long press in OnTouchListener


I already found this answer: Distinguish between long press and short press on a hardware button But I'm interested in OnTouchListener. The reason is that I want that a short press and release (click) will trigger a certain behavior, but long press (without release) will trigger a motion event to enable the user to drag the widget with their finger.

So I'm using ACTION_MOVE:

val listener = View.OnTouchListener(function = { view, motionEvent ->
    // check if the user gesture upon the view is of moving action
    when (motionEvent.action) {
        MotionEvent.ACTION_DOWN-> {
            // pressed
        }
        MotionEvent.ACTION_MOVE -> {
            view.y = motionEvent.rawY - ((view.height / 2) + 60)
            view.x = motionEvent.rawX - view.width / 2
        }
        MotionEvent.ACTION_UP -> {
            // Released

The problem is that ACTION_MOVE will trigger immediately. Is there an option to invoke it only when the press is long? I thought maybe to use timestamps somehow to enable and disable it but is there a simpler solution?


Solution

  • The Android way to handle this is using a gesture detector. The guide topic Detect common gestures discusses how to do this and provides sample code.

    The only reason you would want to operate at the relatively low level of a touch listener is if the logic you want to implement isn't already packaged up by one of the library gesture detectors. Fortunately for you, long press and dragging are already supported gestures.

    If you want to write your own logic anyway, the source code for the appropriate gesture detectors can provide you a good starting point. However, I strongly discourage that because the logic can be very tricky, with lots of edge cases to get right.

    EDIT: Okay, after reading the documentation, I confess that it isn't quite as simple as I thought. But it shouldn't be very complicated, either. The problem is clear from the documentation for setIsLongpressEnabled (emphasis added):

    Set whether longpress is enabled, if this is enabled when a user presses and holds down you get a longpress event and nothing further.

    So after a long press is detected, you won't get any scrolling call-backs. You can work around this as follows. The usual way a gesture detector is used is to write your onTouchEvent method as follows:

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return if (mDetector.onTouchEvent(event)) {
            true
        } else {
            super.onTouchEvent(event)
        }
    }
    

    You can change this to something like the following:

    var isLongPress = false
    
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (mDetector.onTouchEvent(event)) {
            val action = MotionEventCompat.getActionMasked(event)
            if (isLongPress && action == MotionEvent.ACTION_MOVE) {
                // process the drag
            }    
            return true
        }
        return super.onTouchEvent(event)
    }
    

    Then in the onLongPress call-back, capture the event coordinates and set isLongPress. You can clear isLongPress in the onDown call-back. You should then ignore any call-backs to onScroll because, by definition, they will be happening without a long press.