Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LongClick event happens too quickly. How can I increase the clicktime required to trigger it?

In an application I'm working on, I have the requirement that a user must click & hold a component for a period time before a certain action occurs.

I'm currently using an OnLongClickListener to listen for the longclick, but I find that the length of a click to trigger the OnLongClick event is too short.

For example, let's say the LongClick event triggers after a 400ms click, but I want the user to have to click & hold for 1200ms before the event triggers.

Is there any way I can configure the LongClick event to require a longer click?
Or is there perhaps another construct that would allow me to listen for longer clicks?

like image 875
ampersandre Avatar asked Oct 28 '11 19:10

ampersandre


3 Answers

It is not possible to change the timer on the onLongClick event, it is managed by android itself.

What is possible is to use .setOnTouchListener().

Then register when the MotionEvent is a ACTION_DOWN.
Note the current time in a variable.
Then when a MotionEvent with ACTION_UP is registered and the current_time - actionDown time > 1200 ms then do something.

so pretty much:

Button button = new Button(); long then = 0;     button.setOnTouchListener(new OnTouchListener() {          @Override         public boolean onTouch(View v, MotionEvent event) {             if(event.getAction() == MotionEvent.ACTION_DOWN){                 then = (Long) System.currentTimeMillis();             }             else if(event.getAction() == MotionEvent.ACTION_UP){                 if(((Long) System.currentTimeMillis() - then) > 1200){                     return true;                 }             }             return false;         }     }) 
like image 64
Rohan Avatar answered Sep 29 '22 11:09

Rohan


This is the simplest way that I have found to achieve this behavior. It has a couple of advantages over the currently accepted answer.

  1. By checking view.isPressed we ensure that the onClick and onLongClick are not triggered if the touch event leaves the view. This mimics the default onClick and onLongClick behavior of the system.
  2. The event already stores timing information so there is no need to store the start time on ACTION_DOWN or calculate the current time on ACTION_UP ourselves. This means that we can use it for multiple views at the same time because we aren't tracking the event start time in a single variable outside of onTouch.

NOTE: ViewConfiguration.getLongPressTimeout() is the default and you can change that check to use any value you want.

NOTE: If the view is not normally clickable you will need to call view.setClickable(true) for the view.isPressed() check to work.

@Override
public boolean onTouch(View view, MotionEvent event) {
    if (view.isPressed() && event.getAction() == MotionEvent.ACTION_UP) {
        long eventDuration = event.getEventTime() - event.getDownTime();
        if (eventDuration > ViewConfiguration.getLongPressTimeout()) {
            onLongClick(view);
        } else {
            onClick(view);
        }
    }
    return false;
}

If you, like @ampersandre, would like the long click event to trigger immediately once the delay period is reached instead of waiting for ACTION_UP then the following works well for me.

@Override
public boolean onTouch(View view, MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        view.setTag(true);
    } else if (view.isPressed() && (boolean) view.getTag()) {
        long eventDuration = event.getEventTime() - event.getDownTime();
        if (eventDuration > ViewConfiguration.getLongPressTimeout()) {
            view.setTag(false);
            onLongClick(view);
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            onClick(view);
        }
    }
    return false;
}
like image 31
mpkuth Avatar answered Sep 29 '22 13:09

mpkuth


I worked out a solution with the help of Rohan :)
I adapted his answer to fit my requirements.

When the user pushes the button, a thread is started. The thread sleeps for my desired delay, then when it wakes up, it executes whatever code I need it to do. When the user lets go, the thread is killed. This accomplishes what I want, because if the user lets go before the thread wakes up, the thread is interrupted and the action doesn't occur.

I like this approach because it lets me execute my business logic as soon as the delay is up, which is good because I can give the user some feedback letting them know they've pushed long enough (the phone can vibrate, for example).
The downside to this approach is: there is a risk that the user lets go of the button while your desired action is running, and kills the thread before everything is done. This isn't a huge problem for my case, because my business logic does very little; it just fires an event for some other class to process. If the action didn't complete fully, it's acceptable for the user to have to try again.

The code is a little longer than I'd like, but if this is a common feature in your application, it's easily re-useable. Here's a code example:

protected class MyLongClickListener implements View.OnTouchListener {
    private Thread longClickSensor;

    public boolean onTouch(View view, MotionEvent event) {
        // If the user is pressing down and there is no thread, make one and start it
        if (event.getAction() == MotionEvent.ACTION_DOWN && longClickSensor == null) {
            longClickSensor = new Thread(new MyDelayedAction());
            longClickSensor.start();
        }
        // If the user has let go and there was a thread, stop it and forget about the thread
        if (event.getAction() == MotionEvent.ACTION_UP && longClickSensor != null) {
            longClickSensor.interrupt();
            longClickSensor = null;
        }
        return false;
    }

    private class MyDelayedAction implements Runnable {
        private final long delayMs = 1200;

        public void run() {
            try {
                Thread.sleep(delayMs); // Sleep for a while
                doBusinessLogic();     // If the thread is still around after the sleep, do the work
            } catch (InterruptedException e) { return; }
        }
        private void doBusinessLogic() {
            // Make sure this logic is as quick as possible, or delegate it to some other class
            // through Broadcasted Intents, because if the user lets go while the work is happenening,
            // the thread will be interrupted.
        }
    }
}
like image 31
ampersandre Avatar answered Sep 29 '22 11:09

ampersandre