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 curren
I found a simple solution studying how the long press event works.
Each time a view is clicked, a Runnable
of type CheckForLongPress
is added to a queue with a delay. If the delay ends, the OnLongClickListener
is called. If there is a different event before the delay ends, then, the CheckForLongPress Runnable
is removed from de queue.
I just override the public method postDelayed(Runnable action, long delayMillis)
of the view to change the OS delay
@Override public boolean postDelayed(Runnable action, long delayMillis) {
boolean isLongPress = action.getClass().getSimpleName().equals("CheckForLongPress");
return super.postDelayed(action, isLongPress ? LONG_PRESS_MILLIS : delayMillis);
}
I set LONG_PRESS_MILLIS
to 100 and it's working!
Hope it helps!!! ;)
I found this method. It is so simple.
private static final long HOLD_PRESS_TIME = 1200; //ms unit
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_control);
//Declare your button and add listener
ImageView iconImg = findViewById(R.id.more_icon);
iconImg.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
Log.w("Button", "Button is pressed...");
//Start timer
timer.start();
break;
case MotionEvent.ACTION_UP:
Log.w("Button", "Button is released...");
//Clear timer
timer.cancel();
break;
}
return false;
}
});
}
private CountDownTimer timer = new CountDownTimer(HOLD_PRESS_TIME, 200) {
@Override
public void onTick(long l) {
Log.w("Button", "Count down..."+l); //It call onFinish() when l = 0
}
@Override
public void onFinish() {
//TODO your action here!
Log.w("Button", "Count finish...");
}
};
One method can do it. It's simple to use, easy to configure the time length, on time to trigger the callback.
public static void setOnLongClickListener(final View view, final View.OnLongClickListener longClickListener, final long delayMillis)
{
view.setOnTouchListener(new View.OnTouchListener()
{
final Handler handler = new Handler();
final Runnable runnable = new Runnable()
{
@Override
public void run()
{
longClickListener.onLongClick(view);
mRunning = false;
}
};
boolean mRunning;
boolean mOutside;
RectF mRect = new RectF();
@Override
public boolean onTouch(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
handler.postDelayed(runnable, delayMillis);
mRunning = true;
mOutside = false;
mRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
break;
}
case MotionEvent.ACTION_MOVE:
if (!mOutside)
{
mOutside = !mRect.contains(v.getLeft() + event.getX(), v.getTop() + event.getY());
if (mOutside)
{
handler.removeCallbacks(runnable);
mRunning = false;
}
}
break;
case MotionEvent.ACTION_UP:
{
if (mRunning)
v.performClick();
handler.removeCallbacks(runnable);
mRunning = false;
break;
}
case MotionEvent.ACTION_CANCEL:
{
handler.removeCallbacks(runnable);
mRunning = false;
break;
}
}
return true; // !!!
}
});
}
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.
}
}
}
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;
}
})