Android MotionEvent: find out if motion happened outside the view
Asked Answered
A

7

13

I have a button and OnTouchListener attached to it. How can I find if motion (when user presses the button) happened inside or outside it? Both event.getAction() and event.getActionMasked() return only 0, 1 or 2, which is ActionDown, ActionUp, ActionMove, respectively. There's a constant MotionEvent.ACTION_OUTSIDE, which is 4, but somehow I don't receive it even if I drag touch outside the button - I still receive 2 from both methods. What's the problem?

UPD: I' ve found nice solution - just check focused state on view after ACTION_UP. If it's not focused, it means that movement happened outside the view.

Aubervilliers answered 23/6, 2012 at 19:3 Comment(0)
D
9

That flag only applies to Windows, not Views. You will get ACTION_MOVE when you move your finger off the View, the event stays in the View it originated with. Look at the source code for SeekBar if you need clarification: even if you move your finger off the bar, the thumb still drags!

For doing this at the Window level use FLAG_WATCH_OUTSIDE_TOUCH, it works just fine.

Daphnedaphnis answered 23/6, 2012 at 19:15 Comment(0)
G
15

The MotionEvent.ACTION_OUTSIDE does not works for View's.

One solution is get the X and Y touch position and verify if it is inside the bounds of the View. It can be done like that:

@Override
public boolean onTouchEvent(MotionEvent e) {
    if (isInside(myView, e))
        Log.i(TAG, "TOUCH INSIDE");
    else
        Log.i(TAG, "TOUCH OUTSIDE");
    return true;
}

private boolean isInside(View v, MotionEvent e) {
    return !(e.getX() < 0 || e.getY() < 0
            || e.getX() > v.getMeasuredWidth()
            || e.getY() > v.getMeasuredHeight());
}
Gilolo answered 10/9, 2016 at 17:21 Comment(1)
isInside implementation is not working properly, parameters are not passed to isInside function, that answer really needs editing. There is one that worked for me https://mcmap.net/q/903431/-knowing-whether-a-motionevent-occurred-inside-a-viewHuntington
D
9

That flag only applies to Windows, not Views. You will get ACTION_MOVE when you move your finger off the View, the event stays in the View it originated with. Look at the source code for SeekBar if you need clarification: even if you move your finger off the bar, the thumb still drags!

For doing this at the Window level use FLAG_WATCH_OUTSIDE_TOUCH, it works just fine.

Daphnedaphnis answered 23/6, 2012 at 19:15 Comment(0)
M
8

case MotionEvent.ACTION_CANCEL worked for me.

Margie answered 10/4, 2013 at 12:38 Comment(0)
B
4

If the OnTouchListener is on the Button, you will only receive motion events from within the Button. The MotionEvent.ACTION_OUTSIDE will only be called when the motion event first goes outside the bounds of the View, and you should treat it like it was an ACTION_UP.

Barner answered 23/6, 2012 at 19:16 Comment(0)
L
1
public static boolean touchWithinBounds(MotionEvent event, View view) {
    if (event == null || view == null || view.getWidth() == 0 || view.getHeight() == 0)
        return false;

    int[] viewLocation = new int[2];
    view.getLocationOnScreen(viewLocation);
    int viewMaxX = viewLocation[0] + view.getWidth() - 1;
    int viewMaxY = viewLocation[1] + view.getHeight() - 1;
    return (event.getRawX() <= viewMaxX && event.getRawX() >= viewLocation[0]
        && event.getRawY() <= viewMaxY && event.getRawY() >= viewLocation[1]);
}

Solution for when you're forwarding a touch event from a different view

Lavadalavage answered 3/11, 2017 at 12:6 Comment(0)
E
0

Try this One

private View.OnTouchListener handleTouch = new View.OnTouchListener() {

@Override
public boolean onTouch(View v, MotionEvent event) {

    int x = (int) event.getX();
    int y = (int) event.getY();

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i("TAG", "touched down");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i("TAG", "moving: (" + x + ", " + y + ")");
            break;
        case MotionEvent.ACTION_UP:
            Log.i("TAG", "touched up");
            // do what you want when touch active
            break;
        case MotionEvent.ACTION_CANCEL:
            Log.if("TAG","touched canceled")
            // outside of the view 
            //ACTION_MOVE present 
            break:

    }

    return true;
}

};

Epicurean answered 30/1, 2022 at 17:20 Comment(0)
H
0

Here is a Kotlin extension function you can use from your touch listener to check if the provided event is inside the given view:

private fun MotionEvent.isInside(v: View): Boolean {
    return Rect().apply(v::getGlobalVisibleRect).contains(rawX.toInt(), rawY.toInt())
}
Huntington answered 5/2, 2023 at 19:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.