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?
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; } })
This is the simplest way that I have found to achieve this behavior. It has a couple of advantages over the currently accepted answer.
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.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;
}
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.
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With