Reputation: 14494
We have an Android app that is meant to be mounted on the dash of a vehicle that is operating in rough terrain, so everything is really shaky. We've found that making a single tap on the screen in this kind of situation is difficult, as the taps are often interpreted as small drags.
What I need is for touch events that have a little wiggle before the finger comes up to be interpreted as clicks, not drags. I've been doing some reading into the way the 'touch slop' works in Android and I can see that they already account for this. All I really need to understand is how to increase the 'touch slop' for a subclass of an Android button
widget.
Is this possible in just a couple of lines of code? Or do I need to do my own implementations of onInterceptTouchEvent
and 'onTouchEvent`? If the latter, can anyone give me some direction on how this would work?
Upvotes: 6
Views: 2838
Reputation: 472
For our simple use-case with a android.opengl.GLSurfaceView
, we solved the same problem you have by saying, "if the distance moved in the gesture is less than some_threshold, interpret it as a click". We used standard Euclidean distance between two 2D points = sqrt((deltaX)^2 + (deltaY)^2))
, where deltaX
and deltaY
are the X and Y components of the distance moved in the user's motion gesture.
More precisely, let (x1,y1)
be coordinates where user's finger gesture 'started' and let (x2,y2)
be coordinates where the gesture 'ended'.
Then, deltaX = x2 - x1
(or it can also be x1 - x2
but sign doesn't matter for the distance 'cz we square the value) and similarly for deltaY
.
From these deltas, we calculate the Euclidean distance and if it's smaller than a threshold, the user probably intended it to be a click but because of the device's touch slop, it got classified as a move gesture instead of click.
Implementation-wise, yes we overrode android.view.View#onTouchEvent(MotionEvent)
and
(x1,y1)
when masked action was android.view.MotionEvent#ACTION_POINTER_DOWN
or android.view.MotionEvent#ACTION_DOWN
android.view.MotionEvent#ACTION_MOVE
, (x2,y2)
are readily available from the event's getX
and getY
methods (see corresponding documentation)Upvotes: 1
Reputation: 1805
Here is what I did, hope that help you guys.
private Rect mBtnRect;
yourView.setOnTouchListener(new OnTouchListener() {
private boolean isCancelled = false;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isCancelled = false;
parent.requestDisallowInterceptTouchEvent(true); // prevent parent and its ancestors to intercept touch events
createClickArea(v);
// your logic touch down
return true;
case MotionEvent.ACTION_UP:
if(!isCancelled) {
// Click logic
}
// release mBtnRect when cancel or up event
mBtnRect = null;
return true;
case MotionEvent.ACTION_CANCEL:
isCancelled = true;
releaseTouch(parent, v);
return true;
case MotionEvent.ACTION_MOVE:
if(!isBelongTouchArea(event.getRawX(), event.getRawY())) {
isCancelled = true;
releaseTouch(parent, v);
}
return true;
default:
break;
}
}
// Create the area from the view that user is touching
private final void createClickArea(View v) {
// for increase rect area of button, pixel in used.
final int delta = (int) mContext.getResources().getDimension(R.dimen.extension_area);
final int[] location = new int[2];
// Get the location of button call on screen
v.getLocationOnScreen(location);
// Create the rect area with an extension defined distance.
mBtnRect = new Rect(v.getLeft() - delta, location[1] + v.getTop() - delta, v.getRight(), location[1] + v.getBottom() + delta);
}
//Check the area that contains the moved position or not.
private final boolean isBelongTouchArea(float rawX, float rawY) {
if(mBtnRect != null && mBtnRect.contains((int)rawX, (int)rawY)) {
return true;
}
return false;
}
private void releaseTouch(final ListView parent, View v) {
parent.requestDisallowInterceptTouchEvent(false);
mBtnRect = null;
// your logic
}
Upvotes: 1