Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MotionEvent.ACTION_CANCEL is not triggered during onTouch

I am having this problem where ACTION_CANCEL is not triggered, I have implemented it in my other project and it's working fine. It seems that ACTION_UP is the only MotionEvent that is called after ACTION_DOWN. I wanted to trigger ACTION_CANCEL once my finger is not anymore in the view or outside the screen.

Sample scenario: I click on the view which is a LinearLayout btw, on ACTION_DOWN its background is changed to a "clicked/dimmed" version of the image and when ACTION_UP is triggered its background changes back to the default image only if the finger is within the LinearLayout. Now the problem is when I press it and kept my finger on the screen, and drag my finger outside the LinearLayout, ACTION_UP is still triggered where it shouldn't have.

Here's my code:

    dimView.setOnTouchListener(new OnTouchListener() {
        public boolean onTouch(final View view,
                final MotionEvent motionEvent) {
            if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
                Log.d("TAG", "DOWN");
                return true;
            } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
                Log.d("TAG", "UP");
                return true;
            } else if (motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
                Log.d("TAG", "CANCEL");
                return true;
            }
            return false;
        }
    });

where: dimView is a LinearLayout

like image 286
Compaq LE2202x Avatar asked Oct 17 '13 03:10

Compaq LE2202x


1 Answers

I've been debugging this for a really long time and it bothered me a lot since this came up very randomly. I then tested my code on different devices and realized the API implementation changed with Android 4.2. The expected behaviour and code work absolutely fine with Android 4.1.2 (tested on Galaxy Tab 2) but the bug you describe can be seen on a Nexus 7 (Android 4.2). Apparently Android changed the way MotionEvents are handled in API 17.

One particular case when the bug does not occur is when the view is located in a GroupLayout under a ScrollView. When scrolling is possible the ACTION_CANCEL gets fired. However when no scrolling is possible the bug persists.

At first I tried combining an OnClickListener and OnTouchListener so that the last can handle just the animations but to no avail. Dispatching the Events from parents also doesn't work.

One workaround is to capture ACTION_MOVE events and check if the finger is located outside of the view's boundaries using v.getX() and v.getY() and comparing them to event.getX() and event.getY() respectably. A global boolean variable (isOutside) can be used to store the most current information. Before firing up the ACTION_UP you can check the latest state of isOutside and perform your animations and action accordingly. You can also return true or false depending on whether you captured the event or not.

Update: After digging a bit here i found this solution: Android: Detect if user touches and drags out of button region? and compiled this code. The idea is the same except that it creates a rectangle and checks if the event boundaries are within the rectangle of the view.

    someView.setOnTouchListener(new View.OnTouchListener() {

                private Rect rect;

                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    Log.d(TAG,"Touched: "+event.getAction());
                    if (event.getAction() == MotionEvent.ACTION_DOWN) {
                        Log.d(TAG,"ACTION_DOWN");
                        animateImageButtonOnClick(v, event);
                        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
                    }

                    if (event.getAction() == MotionEvent.ACTION_UP) {
                        Log.d(TAG,"ACTION_UP");
                        if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
                            Log.d(TAG,"ACTION_UP - outside");
                            animateImageButtonOnRelease(v, event);
                        }  else {
                            Log.d(TAG,"ACTION_UP - inside");
                            // do your stuff here  
                        }
                    }

                    if(event.getAction() == MotionEvent.ACTION_MOVE){
                        if(!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())){
                            animateImageButtonOnReleaseWithLowDuration(v, event);
                        }
                    }

                    if (event.getAction() == MotionEvent.ACTION_CANCEL){
                        Log.d(TAG,"ACTION_CANCEL");
                        animateImageButtonOnRelease(v, event);
                        return true;
                    }

                    return true;
                }
            });
like image 89
Toshe Avatar answered Jan 03 '23 20:01

Toshe