When you mean user presses, do you mean a click? A click is when the user presses down and then immediately lifts up finger. Therefore it is encompassing two onTouch Events. You should save the use of onTouchEvent for stuff that happens on the initial touch or the after release.
Thus, you should be using onClickListener if it is a click.
Your answer is analogous: Use onLongClickListener.
Here is an interesting alternative. In onTouchEvent on every ACTION_DOWN schedule a Runnable to run in 1 second. On every ACTION_UP or ACTION_MOVE, cancel scheduled Runnable. If cancelation happens less than 1s from ACTION_DOWN event, Runnable won't run.
final Handler handler = new Handler();
Runnable mLongPressed = new Runnable() {
public void run() {
Log.i("", "Long press!");
}
};
@Override
public boolean onTouchEvent(MotionEvent event, MapView mapView){
if(event.getAction() == MotionEvent.ACTION_DOWN)
handler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout());
if((event.getAction() == MotionEvent.ACTION_MOVE)||(event.getAction() == MotionEvent.ACTION_UP))
handler.removeCallbacks(mLongPressed);
return super.onTouchEvent(event, mapView);
}
I have a code which detects a click, a long click and movement.
It is fairly a combination of the answer given above and the changes i made from peeping into every documentation page.
//Declare this flag globally
boolean goneFlag = false;
//Put this into the class
final Handler handler = new Handler();
Runnable mLongPressed = new Runnable() {
public void run() {
goneFlag = true;
//Code for long click
}
};
//onTouch code
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.postDelayed(mLongPressed, 1000);
//This is where my code for movement is initialized to get original location.
break;
case MotionEvent.ACTION_UP:
handler.removeCallbacks(mLongPressed);
if(Math.abs(event.getRawX() - initialTouchX) <= 2 && !goneFlag) {
//Code for single click
return false;
}
break;
case MotionEvent.ACTION_MOVE:
handler.removeCallbacks(mLongPressed);
//Code for movement here. This may include using a window manager to update the view
break;
}
return true;
}
I confirm it's working as I have used it in my own application.
The solution by MSquare works only if you hold a specific pixel, but that's an unreasonable expectation for an end user unless they use a mouse (which they don't, they use fingers).
So I added a bit of a threshold for the distance between the DOWN and the UP action in case there was a MOVE action inbetween.
The idea is creating a Runnable for execute long click in a future, but this execution can be canceled because of a click, or move.
You also need to know, when long click was consumed, and when it is canceled because finger moved too much. We use initialTouchX & initialTouchY for checking if the user exit a square area of 10 pixels, 5 each side.
Here is my complete code for delegating Click & LongClick from Cell in ListView to Activity with OnTouchListener:
ClickDelegate delegate;
boolean goneFlag = false;
float initialTouchX;
float initialTouchY;
final Handler handler = new Handler();
Runnable mLongPressed = new Runnable() {
public void run() {
Log.i("TOUCH_EVENT", "Long press!");
if (delegate != null) {
goneFlag = delegate.onItemLongClick(index);
} else {
goneFlag = true;
}
}
};
@OnTouch({R.id.layout})
public boolean onTouch (View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout());
initialTouchX = motionEvent.getRawX();
initialTouchY = motionEvent.getRawY();
return true;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_CANCEL:
if (Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
handler.removeCallbacks(mLongPressed);
return true;
}
return false;
case MotionEvent.ACTION_UP:
handler.removeCallbacks(mLongPressed);
if (goneFlag || Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
goneFlag = false;
return true;
}
break;
}
Log.i("TOUCH_EVENT", "Short press!");
if (delegate != null) {
if (delegate.onItemClick(index)) {
return false;
}
}
return false;
}
ClickDelegateis an interface for sending click events to the handler class like an Activity
And all what you need is to implement it in your Activity or parent Viewif you need to delegate the behavior:
public class MyActivity extends Activity implements ClickDelegate {
//code...
//in some place of you code like onCreate,
//you need to set the delegate like this:
SomeArrayAdapter.delegate = this;
//or:
SomeViewHolder.delegate = this;
//or:
SomeCustomView.delegate = this;
@Override
public boolean onItemClick(int position) {
Object obj = list.get(position);
if (obj) {
return true; //if you handle click
} else {
return false; //if not, it could be another event
}
}
@Override
public boolean onItemLongClick(int position) {
Object obj = list.get(position);
if (obj) {
return true; //if you handle long click
} else {
return false; //if not, it's a click
}
}
}
Here is an approach, based on MSquare's nice idea for detecting a long press of a button, that has an additional feature: not only is an operation performed in response to a long press, but the operation is repeated until a MotionEvent.ACTION_UP message is received. In this case, the long-press and short-press actions are the same, but they could be different.
Note that, as others have reported, removing the callback in response to a MotionEvent.ACTION_MOVE message prevented the callback from ever getting executed since I could not keep my finger still enough. I got around that problem by ignoring that message.
private void setIncrementButton() {
final Button btn = (Button) findViewById(R.id.btn);
final Runnable repeater = new Runnable() {
@Override
public void run() {
increment();
final int milliseconds = 100;
btn.postDelayed(this, milliseconds);
}
};
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
increment();
v.postDelayed(repeater, ViewConfiguration.getLongPressTimeout());
} else if (e.getAction() == MotionEvent.ACTION_UP) {
v.removeCallbacks(repeater);
}
return true;
}
});
}
private void increment() {
Log.v("Long Press Example", "TODO: implement increment operation");
}