How to differentiate between a fling and a touch?
Asked Answered
V

2

6

I have a ListView inside of a ViewFlipper which I am flipping when the user swipes across the screen. Clicking on a ListView will open the browser. Sometimes when I am swiping, it gets detected as a touch on the ListView and will open the browser. This can be annoying. How can I prevent this from happening?

class MyGestureDetector extends SimpleOnGestureListener {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            try {
                if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
                    return false;
                // right to left swipe
                if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    viewFlipper.setInAnimation(slideLeftIn);
                    viewFlipper.setOutAnimation(slideLeftOut);
                    viewFlipper.showNext();
                } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    viewFlipper.setInAnimation(slideRightIn);
                    viewFlipper.setOutAnimation(slideRightOut);
                    viewFlipper.showPrevious();
                }

                if (viewFlipper.getDisplayedChild() == 0) {
                    // TODO: light up left
                    flipperPosition = 0;
                } else if (viewFlipper.getDisplayedChild() == 1) {
                    // TODO: light up middle
                    flipperPosition = 1;
                } else if (viewFlipper.getDisplayedChild() == 2) {
                    // TODO: light up right
                    flipperPosition = 2;
                }
            } catch (Exception e) {
                System.out.println(e);
            }
            return false;
        }
    }

protected MotionEvent downStart = null;  

        public boolean onInterceptTouchEvent(MotionEvent event) {  

            switch(event.getAction()) {  
            case MotionEvent.ACTION_DOWN:  
                // keep track of the starting down-event  
                downStart = MotionEvent.obtain(event);  
                break;  
            case MotionEvent.ACTION_MOVE:  
                // if moved horizontally more than slop*2, capture the event for ourselves  
                float deltaX = event.getX() - downStart.getX();  
                if(Math.abs(deltaX) > ViewConfiguration.getTouchSlop() * 2)  
                    return true;  
                break;  
            }  

            // otherwise let the event slip through to children  
            return false;  
        }  
Vaal answered 30/9, 2010 at 18:54 Comment(2)
A touch lasts just a moment. A fling could last a few days. Usually, a fling is made up of many touches...Linage
Totally unrelated to the problem at hand, but I LOL'd, FrustratedWithFormsDesigner. :^)Jimmyjimsonweed
C
15

The way this is normally done is through the parent view's onInterceptTouchEvent method. onInterceptTouchEvent has a chance to see any touch event before a view's children do. If onInterceptTouchEvent returns true the child view that was previously handling touch events receives an ACTION_CANCEL and the events from that point forward are sent to the parent's onTouchEvent method for the usual handling. It can also return false and simply spy on events as they travel down the view hierarchy to their usual targets.

You want to do essentially this in onInterceptTouchEvent on the parent view where you're detecting the flings:

  • On ACTION_DOWN, record the location of the touch. Return false.
  • On ACTION_MOVE, check the delta between initial touch down position and current position. If it's past a threshold value, (the framework uses ViewConfiguration#getScaledTouchSlop() or other appropriate values from ViewConfiguration for things like this,) return true.
  • Detect and handle the fling as usual based on onTouchEvent.

Once you intercept, the ListView will cancel its touch handling and you won't get unwanted tap events on your list items. ListView is also set up to disallow its parent from intercepting events once the user has started vertically scrolling the list, which means you won't get mistaken horizontal flings if the user sloppily flings the list vertically.

This is how things like the stock Android Launcher or News and Weather do side to side paging of scrolling/tappable content.

Comp answered 30/9, 2010 at 21:23 Comment(4)
i tried implementing onInterceptTouchEvent, but the method never gets called. Check out my updated codeVaal
onInterceptTouchEvent is a View method to override, not a listener method.Comp
I'm not quite sure what you mean? Could you show me an example usage?Vaal
goo.gl/aVzD This is from the stock launcher code in the Android open source project. The Workspace class extends ViewGroup and overrides both onInterceptTouchEvent and onTouchEvent. A VelocityTracker checks for fast motion to signal a fling at the end of a drag scroll. A Scroller is used to move to the final page position after the user lets go. It performs some smoothing that you won't necessarily need but in essence it implements exactly what's described above. Your code won't need to be nearly as involved as this to do what you're trying to do.Comp
I
1

Have you tried using SimpleOnGestureListener.onSingleTapConfirmed(MotionEvent) for the on touch event ("click")? This will only be called after the detector is confident that the user's first tap is really a tap and not a double tap (or hopefully a fling).

class MyGestureDetector extends SimpleOnGestureListener {
    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
        // Code...
    }
}
Ileus answered 30/9, 2010 at 19:51 Comment(1)
I haven't but I'm not sure how I can hook up my ListView to this?Vaal

© 2022 - 2024 — McMap. All rights reserved.