Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Intercept and pass on all touch events

Tags:

android

events

I have an overlay ViewGroup that is the size of the screen which I want to use to show an effect when the user interacts with the app, but still passes the onTouch event to any underlying views.

I am intrested in all MotionEvents (not just DOWN), so onInterceptTouchEvent() does not apply here as if i return true my overlay will consume all events, and if false will only receive the DOWN events (the same applys to onTouch).

I thought I could override the Activitys dispatchTouchEvent(MotionEvent ev) and call a custom touch event in my overlay, but this has the effect of not translating the input coords depending on the position of my view (for example all events will pass appear to be happening 20 or so px below the actual touch as the system bar is not taken into account).

like image 793
Dori Avatar asked Jul 27 '11 09:07

Dori


People also ask

How does Android handle multiple touch?

To support multiple touch pointers, you can cache all active pointers with their IDs at their individual ACTION_POINTER_DOWN and ACTION_DOWN event time; remove the pointers from your cache at their ACTION_POINTER_UP and ACTION_UP events.

How are Android touch events delivered?

The touch event is passed in as a MotionEvent , which contains the x,y coordinates, time, type of event, and other information. The touch event is sent to the Window's superDispatchTouchEvent() . Window is an abstract class. The actual implementation is PhoneWindow .

Which method you should override to control your touch action in Android?

Android supports multiple pointers, e.g. fingers which are interacting with the screen. The base class for touch support is the MotionEvent class which is passed to Views via the onTouchEvent() method. you override the onTouchEvent() method.

In what way gestures are preferred than touch events?

In what way gestures are preferred than touch events? Â Gestures are scripted “solutions” that take advantage of these touch events. Â So instead of tracking two touch points to determine if they're moving away or closer to one another in order to manipulate the size of a photo, you can just use GESTURE_ZOOM.


3 Answers

The only proper way to build such an interceptor is using a custom ViewGroup layout.

But implementing ViewGroup.onInterceptTouchEvent() is just not enough to catch every touch events because child views can call ViewParent.requestDisallowInterceptTouchEvent(): if the child does that your view will stop receiving calls to interceptTouchEvent (see here for an example on when a child view can do that).

Here's a class I wrote (Java, long ago...) and use when I need something like this, it's custom FrameLayout that gives full control on touch events on a delegator

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;

/**
 * A FrameLayout that allow setting a delegate for intercept touch event
 */
public class InterceptTouchFrameLayout extends FrameLayout {
    private boolean mDisallowIntercept;

    public interface OnInterceptTouchEventListener {
        /**
         * If disallowIntercept is true the touch event can't be stealed and the return value is ignored.
         * @see android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent)
         */
        boolean onInterceptTouchEvent(InterceptTouchFrameLayout view, MotionEvent ev, boolean disallowIntercept);

        /**
         * @see android.view.View#onTouchEvent(android.view.MotionEvent)
         */
        boolean onTouchEvent(InterceptTouchFrameLayout view, MotionEvent event);
    }

    private static final class DummyInterceptTouchEventListener implements OnInterceptTouchEventListener {
        @Override
        public boolean onInterceptTouchEvent(InterceptTouchFrameLayout view, MotionEvent ev, boolean disallowIntercept) {
            return false;
        }
        @Override
        public boolean onTouchEvent(InterceptTouchFrameLayout view, MotionEvent event) {
            return false;
        }
    }

    private static final OnInterceptTouchEventListener DUMMY_LISTENER = new DummyInterceptTouchEventListener();

    private OnInterceptTouchEventListener mInterceptTouchEventListener = DUMMY_LISTENER;

    public InterceptTouchFrameLayout(Context context) {
        super(context);
    }

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

    public InterceptTouchFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public InterceptTouchFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyle) {
        super(context, attrs, defStyleAttr, defStyle);
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
        mDisallowIntercept = disallowIntercept;
    }

    public void setOnInterceptTouchEventListener(OnInterceptTouchEventListener interceptTouchEventListener) {
        mInterceptTouchEventListener = interceptTouchEventListener != null ? interceptTouchEventListener : DUMMY_LISTENER;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean stealTouchEvent = mInterceptTouchEventListener.onInterceptTouchEvent(this, ev, mDisallowIntercept);
        return stealTouchEvent && !mDisallowIntercept || super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean handled = mInterceptTouchEventListener.onTouchEvent(this, event);
        return handled || super.onTouchEvent(event);
    }
}

This class provide a setInterceptTouchEventListener() to set your custom interceptor.

When it is required to disallow intercepting touch event it cheats: pass the disallow to the parent view, like it's supposed to, but keeps intercepting them. However, it does not let the listener intercept events anymore, so if the listener return true on intercept touch event that will be ignored (you can choose to throw an IllegalStateException instead if you prefer.

This way you can transparently receive every touch event that pass through your ViewGroup without disrupting the children views behavior.

like image 107
Daniele Segato Avatar answered Sep 20 '22 02:09

Daniele Segato


I solved this with a non perfect solution but works in this situation.

I created another ViewGroup which contains a clickable child which has width and height set to fill_parent (this was my requirement before adding the effects) and have overridden onIntercept...(). In onIntercept... I pass the event on to my overlay view to a custom onDoubleTouch(MotionEvent) method and do the relevant processing there, without interrupting the platforms routing of input events and letting the underlying viewgroup act as normal.

Easy!

like image 41
Dori Avatar answered Sep 22 '22 02:09

Dori


Here's what I did, building on Dori's answer I used onInterceptTouch . . .

float xPre = 0;

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    if(Math.abs(xPre - event.getX()) < 40){
        return false;
    }
    if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_UP  || event.getAction() == MotionEvent.ACTION_MOVE){
        xPre = event.getX();
    }
    return true;
}

Basically, when any important movement is made, remember where it happened, via xPre; then if that point is close enough to the next point to be say, an onClick/onTouch of the child view return false and let onTouch do it's default thing.

like image 21
Chris Sullivan Avatar answered Sep 18 '22 02:09

Chris Sullivan