Search code examples
androidgridviewdialogmotioneventlong-press

Registering UP/CANCEL from Dialog when DOWN event was triggered from a View's LongPress


I have a UX requirement that the user triggers a Dialog by long pressing a cell in a GridView.

  • While the Dialog is displayed the user must be able to move their finger/thumb around the screen without triggering the UP/CANCEL event when they leave the bounds of GridView cell.

  • When the user finally breaks contact with the screen is what I'm looking to capture. GridView seems to register some false positives for UP/CANCEL that we don't see using any other views.

  • The issue is that the original view captures all the touch events because the DOWN was captured by it.

  • The dialog registers/sees no touches until after the UP event from the original view.

I have tried cancelling the original touch event and using dispatchTouch(), etc. No joy.

Any ideas?


Solution

  • I have used something like this in one of my projects.

    Assign an OnTouchListener to every cell of your gridView and override the OnTouch method.

    @Override
    public boolean onTouch(View v, MotionEvent event) {
    
    boolean isLongPressed;
    
    int mSwipeSlop = ViewConfiguration.get(context).
                    getScaledTouchSlop();
    
    boolean mSwiping;
    
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            handler.postDelayed(mLongPressed, 1000);
            break;
    
        case MotionEvent.ACTION_CANCEL:
            handler.removeCallbacks(mLongPressed);
            break;
    
        case MotionEvent.ACTION_MOVE:
            float x = event.getX() + v.getTranslationX();
                float deltaX = x - mDownX;
                float deltaXAbs = Math.abs(deltaX);
                float y = event.getY() + v.getTranslationY();
                float deltaY = Y - mDownY;
                float deltaYAbs = Math.abs(deltaY);
                float absDist = Math.sqrt(Math.pow(deltaXAbs, 2) + Math.pow(deltaXAbs, 2));
                if (!mSwiping) {
                    if (absDist > mSwipeSlop) {
                        mSwiping = true;
                        handler.removeCallbacks(mLongPressed);
                    }
                }
            break;
    
        case MotionEvent.ACTION_UP:
            handler.removeCallbacks(mLongPressed);
            if (isLongPressed) {
                 // DO ACTION UP
            }
            break;
    
        default: 
            return false;
        }
    return true;
    }
    

    Open the dialog in the runnable mLongPressed, which will only run if the user has touched the same spot for a second. You can change the distance he can move and the time he needs to press to register as a long click of course. However, I would recommend using getScaledTouchSlop() for the distance.

    final Handler handler = new Handler(); 
    Runnable mLongPressed = new Runnable() { 
        public void run() { 
            // OPEN DIALOG
            isLongPressed = true;
        }   
    };
    

    By using this code in my project, the user can move his finger around the whole screen without ACTION_UP getting triggered. Only when he lifts his finger, it is triggered.