Mick Byrne
Mick Byrne

Reputation: 14494

Increase touch slop in Android button

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

Answers (2)

aasu
aasu

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

  • recorded (x1,y1) when masked action was android.view.MotionEvent#ACTION_POINTER_DOWN or android.view.MotionEvent#ACTION_DOWN
  • and when masked action was android.view.MotionEvent#ACTION_MOVE, (x2,y2) are readily available from the event's getX and getY methods (see corresponding documentation)

Upvotes: 1

NamNH
NamNH

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

Related Questions