Search code examples
javaandroidtouch

GestureDetector.SimpleOnGestureListener not respecting View scale factor?


I am using a SimpleOnGestureListener to detect the onSingleTapUp event an a View.

The view has a scale factor of 5, therefore 1 screen pixel corresponds to 5 pixels on my view:

  view.setScaleX(5);
  view.setScaleY(5);

The problem I am facing is that the Tap event is not detected accurately. I looked in the source code of SimpleOnGestureListener, the relevant parts are:

  • Here the SingleTapUp() listener is called if the touch points haven't moved over a certain threshold
  • Here the travel distance of the touch points are calculated

I think the reason why the Tap is not detected reliably is that the distance calculation for the touch points relies on the scaled local coordinates of the view (e.getX() and e.getY()) instead of the raw coordinates (e.getRawX() and e.getRawY()).

Due to the scale factor tiny movements of the finger on screen will cause large changes in e.getX() and e.getY().

Is my interpretation of the code correct? If so, how can I work around this problem?

For now my workaround is to intercept all events on a View that has no scale factor and then do the dispatching of the MotionEvents myself to the views that have the scale factor.

It works well, still I'd be interested if my analysis of the android code is correct or not.

I am using android 4.4


Solution

  • IMHO, your analysis of code is correct!

    Just some additional information which was found exploring source code:

    • distance defined in variable mTouchSlopSquare and initialized here (stored as square of original value, just for optimizations)
    • if you pass Context to GestureDetector's constructor (which should be, because second one is obsolete) then this value equals to com.android.internal.R.dimen.config_viewConfigurationTouchSlop according to this line
    • comparison is touch in same area done in this line

    Workaround

    As workaround I suggest you to access to private member mTouchSlopSquare of GestureDetector and add scale factor this distance calculation.

    See my code below:

    // Utility method
    public static boolean fixGestureDetectorScale(GestureDetector detector, float scale) {
        try {
            Field f = GestureDetector.class.getDeclaredField("mTouchSlopSquare");
            f.setAccessible(true);
    
            int touchSlopSquare = (int) f.get(detector);
            float touchSlop = (float) Math.sqrt(touchSlopSquare);
            //normalize touchSlop
            touchSlop /= scale;
            touchSlopSquare = Math.round(touchSlop * touchSlop);
            f.set(detector, touchSlopSquare);
    
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            return false;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    

        // usage
        fixGestureDetectorScale(mGestureDetector, scale);
        view.setScaleX(scale);
        view.setScaleY(scale);
    

    I've checked and it works for me.