10

How can I find the view causing a MotionEvent ACTION_CANCEL? I have a view "A" which is receiving ACTION_CANCEL and I don't want that to happen. Somewhere, a view "B" is "consuming" the MotionEvent. I'm hoping there is a way to find out who is "B" so I can address this misfunctionality.

I've tried looking through my code for various OnTouchEvent() and OnInterceptTouchEvent() handlers but haven't yet found a culprit.

I've also put a break point at the problematic ACTION_CANCEL but am not able to recognize anything in the MotionEvent that might represent "B".

Peri Hartman
  • 19,314
  • 18
  • 55
  • 101
  • Are you getting the initial `ACTION_DOWN` event? – Barend Jul 21 '15 at 19:59
  • Yes, and a series of ACTION_MOVE. – Peri Hartman Jul 21 '15 at 20:05
  • I was going to ask if you could just peek at the View underneath the initial event, but I've realised in the mean time that that's not going to help, so never mind, sorry. – Barend Jul 21 '15 at 20:06
  • 1
    do you mind showing us some code we can start with in respect your problem. – Elltz Sep 03 '15 at 16:30
  • This question is independent of my code. It applies to anyone's code using MotionEvent. Simply put a breakpoint where you handle ACTION_CANCEL. Then, my question is: how do you determine who caused the ACTION_CANCEL? It may not come from any code you have written ! – Peri Hartman Sep 03 '15 at 17:48
  • For those who wonder: I did find the culprit. It was a ScrollView that was in the parent hierarchy. It was left over from an earlier design approach and wasn't needed so I took it out. Presto - the ACTION_CANCELs disapppeared ! – Peri Hartman Sep 04 '15 at 14:03
  • If anyone at Google is reading this, pay attention: please add something to MotionEvent that tracks the source of ACTION_CANCEL !!!!!!!!! – Peri Hartman Sep 04 '15 at 14:04

3 Answers3

7

If a parent is intercepting the motion event, the only way to prevent this is to prevent the parent from intercepting that event. This can be managed well in two ways.

Without seeing specific code and wanting a generalised solution I would suggest the following.

I would suggest managing your touch events for the parents and the child by managing the
requestDisallowInterceptTouchEvent(boolean) and

onInterceptTouchEvent(android.view.MotionEvent) event handlers of every view/viewgroup within the affected views A, B C.

By disallowing parent intercepts in the child, this assists you in catching parent intercepts you haven't accounted for, and also to customise and vary the child elements from within one parent.

This must be managed from your highest parent of your view/viewGroup and managed through all parent and child relationships.

Checking for listviews, any element that has a inbuilt touch events.

android.com/training/gestures/viewgroup

In terms of finding the offending view that is intercepting the event, that cannot be answered except by the logic of:

Go through each parent to child/parent to child view. Methodically check the ontouch handling within each view/view group as shown in my diagram.

View diagram

There is some more detail in these answers here:
https://stackoverflow.com/a/30966413/3956566
https://stackoverflow.com/a/6384443/3956566

I'm sure you understand this, but to me, it's the simplest solution.

Beyond this, we'd need to look at your code to troubleshoot it.

Community
  • 1
  • 1
  • Well, it appears that the answer to my question is "there is no direct method". In other words, by the time the ACTION_CANCEL is received by some arbitrary view, the source of the ACTION_CANCEL is lost. Too bad. I do appreciate your suggested methodology and it is in fact more-or-less what I have been doing - basically looking at the chain of parents to see who could cancel. Very little of it is my code (it's android layouts and widgets) so it becomes difficult to wade through "foreign" source code to speculate on the ACTION_CANCEL origin. – Peri Hartman Sep 04 '15 at 14:00
  • I don't really feel like you answered the question but you came the closest and you put in a great effort. I hate to see the bounty wasted, too. So, cheers! – Peri Hartman Sep 04 '15 at 14:01
2

If I got your question correct, you are receiving ACTION_CANCEL probably in the parent and you need to find that view. Given event X and Y, you can find view that contains these coordinates at the first moment ACTION_CANCEL occurred. Try calling this method either with top parent (android.R.id.content) or the ViewGroup you are dealing with.

private View findViewAt(View contentView, int eventX, int eventY) {
            ArrayList<View> unvisited = new ArrayList<View>();
            unvisited.add(contentView);
            while (!unvisited.isEmpty()) {
                View child = unvisited.remove(0);
                if(isViewContains(child, eventX, eventY) {
                    Log.i(TAG, "view found! ");
                    unvisited.clear();
                    return child;
                }
                if (!(child instanceof ViewGroup)){
                    continue;
                }
                ViewGroup group = (ViewGroup) child;
                final int childCount = group.getChildCount();
                for (int i=0; i< childCount; i++){
                    unvisited.add(group.getChildAt(i));
                }
            }
      return null;
    }


private boolean isViewContains(View view, int eventX, int eventY) {
    int[] location = new int[2];
    view.getLocationOnScreen(location);
    int x = location[0];
    int y = location[1];
    int width = view.getWidth();
    int height = view.getHeight();
    return eventX < x || eventX > x + width || eventY < y || eventY > y + height;
}
Nikola Despotoski
  • 49,966
  • 15
  • 119
  • 148
  • This is a good idea. However, two comments. (1) Aren't eventX, eventY in view-local coordinates? For isViewContains() to work, we need screen coordinates. (2) I think the algorithm can be done directly with a tree walk - no need to for a queue - or am I missing something. – Peri Hartman Jul 21 '15 at 20:53
  • This will walk all views in hierarchy. – Nikola Despotoski Jul 21 '15 at 20:55
  • Agreed, though point 2 still holds. Regardless, point 1 is the clincher. If eventX, eventY are in local coords, I don't see a way to tell what view they came from. – Peri Hartman Jul 21 '15 at 21:09
  • If they are local, youcan try to find coordinars relative to the screen given Rect of the view to which coordinates of touch evet are given. – Nikola Despotoski Jul 21 '15 at 21:12
  • That would be nice but how do you find the rect for the view? I've looked carefully at MotionEvent and don't see any such method. – Peri Hartman Jul 21 '15 at 23:21
  • X relative to screen should be view.getLeft() + eventCancel.getX() and y is view.getTop() + eventCancel.getY(), where view is the view that first ACTION_DOWN occured. Note that this is just an idea. – Nikola Despotoski Jul 23 '15 at 17:33
  • I don't think so. getLeft(), etc., are relative to the parent view. Plus, I think you have circular logic. I think this path is going to be a dead end. I appreciate your thoughts and gave you +1. – Peri Hartman Jul 23 '15 at 22:49
  • 1
    I'm not sure how this algorithm can return a 100% accurate response. What if within View A, there is View B, and within view B there is view C. Either view B or view C could have captured the event - and if View B & view C have overlapping rectangles, this algorithm could return an incorrect response. – Gil Moshayof Aug 30 '15 at 09:19
  • and to add to @GilMoshayof if the user specifies that an Activity not re-draw itself after orientation change-(rotate the device) getLocationOnScreen will return _random numbers_ – Elltz Sep 01 '15 at 03:13
1

On my experience , a case that makes A view receive a ACTION_CANCEL event is after A is touched by users and they drags their finger out of area of A, If you face this case,Add a method to check the location on dispatchTouchEvent() can help.

Livekus
  • 63
  • 7