Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to prevent TouchEvent from scrolling

Tags:

android

I have a custom view, where I have functionality with touch events (swipe etc.). Now it can happen, that this custom view is used within a ScrollableLayout. The problem then is, that when the user swipes inside my custom view, the parent (ScrollableLayout) will also handle the swipe gesture and so it scrolls, but it should not.

I need something like event.preventDefaults() from JavaScript.

I override View#onTouchEvent and return always true. I thought, that when I return true from onTouchEvent this means that the event is consumed and no other view will get the onTouchEvent, but that's wrong.

Can anybody help me?

My View is quite easy to test it:

public class PreventingTouchEventView extends View {

    public PreventingTouchEventView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.RED);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(200, 200);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }
}

I pushed a example android project to github: https://github.com/jjoe64/android-preventing-touch-test

If you touch and scroll inside the red canvas, the ScrollableLayout should not scroll.

like image 290
appsthatmatter Avatar asked Jul 24 '13 14:07

appsthatmatter


2 Answers

To prevent touches from that layout you have several options:

1) set OnTouchListener, which would always return true.

2) override dispatchTouchEvent(MotionEvent event) to always return true

UPD: Okey, normally you would have to set OnTouchListener, which would always return true, as I said previously to prevent other views from being called onTouch, but with ScrollView it's completely another story.

First of all I will tell how dispatchTouchEvent works. It starts dispatching event from the root view to it's child views, so that means that dispatchTouchEvent of parent is called BEFORE dispatchTouchEvent of it's children.

Then in ViewGroup.dispatchTouchEvent() there is a piece of code

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
    intercepted = onInterceptTouchEvent(ev);
    ev.setAction(action); // restore action in case it was changed
} else {
    intercepted = false;
}

which decides if this touch event should be intercepted, which means if intercepted flag is true, then this view would intercept all successive event. And in ScrollView onInterceptTouchEvent implemented to intercept actions if they are perfroming vertical scrolling.

So my first idea was to set FLAG_DISALLOW_INTERCEPT to the parent of your custom view to disallow this opportunity of intercepting events, but this idea fails because of the next lines of code in ViewGroup:

if (actionMasked == MotionEvent.ACTION_DOWN) {
     // Throw away all previous state when starting a new touch gesture.
     // The framework may have dropped the up or cancel event for the previous gesture
    // due to an app switch, ANR, or some other state change.
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

So that means that after the MotionEvent.ACTION_DOWN all states are cleared (and so do FLAG_DISALLOW_INTERCEPT is also cleared).

So the final solution is pretty simple

public class PreventingTouchEventView extends View {

    public PreventingTouchEventView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.RED);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (getParent() != null && event.getAction() == MotionEvent.ACTION_DOWN) {
            getParent().requestDisallowInterceptTouchEvent(true);
        }
        return super.dispatchTouchEvent(event);
    }
}

this would raise FLAG_DISALLOW_INTERCEPT right after first MotionEvent.ACTION_DOWN, which would dissallow further interception of future actions by parent.

But note, that if your parent view doing some crazy stuff on MotionEvent.ACTION_DOWN and intercepting this event then you just can't do anything about it, because your parent's dispatchTouchEvent are called before your dispatchTouchEvent.

like image 78
Desert Avatar answered Nov 02 '22 23:11

Desert


you have to override parent's onInterceptTouchEvent method

like image 37
pskink Avatar answered Nov 03 '22 00:11

pskink