19

I needed a singletap touch detect in my custom view's ontouch method. I tried getting the x and y values in both ACTION-DOWN and ACTION-UP and in ACTION-UP I gave a condition that if a the values of X and Y in ACTIONDOWN and ACTION-UP are equal then take it as a single tap.

My code is as follows

@Override
public boolean onTouchEvent(MotionEvent ev) {
   if (!mSupportsZoom && !mSupportsPan) return false;

    mScaleDetector.onTouchEvent(ev);

    final int action = ev.getAction();
    switch (action & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN: {
        final float x = ev.getX();
        final float y = ev.getY();

        mLastTouchX = x;  //here i get x and y values in action down
        mLastTouchY = y;
        mActivePointerId = ev.getPointerId(0);

        break;
    }

    case MotionEvent.ACTION_MOVE: {
        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
        final float x = ev.getX(pointerIndex);
        final float y = ev.getY(pointerIndex);

        if (mSupportsPan && !mScaleDetector.isInProgress()) {
            final float dx = x - mLastTouchX;
            final float dy = y - mLastTouchY;

            mPosX += dx;
            mPosY += dy;
            //mFocusX = mPosX;
            //mFocusY = mPosY;

            invalidate();
        }

        mLastTouchX = x;
        mLastTouchY = y;

        break;
    }

    case MotionEvent.ACTION_UP: {

        final float x = ev.getX();
        final float y = ev.getY();

        touchupX=x;   //here is get x and y values at action up
        touchupY=y; 

        if(mLastTouchX == touchupX && mLastTouchY == touchupY){  //my condition if both the x and y values are same .

            PinchZoomPanActivity2.tapped1(this.getContext(), 100); //my method if the singletap is detected

        }
        else{

        }

        mActivePointerId = INVALID_POINTER_ID;

        break;
    }

    case MotionEvent.ACTION_CANCEL: {
        mActivePointerId = INVALID_POINTER_ID;
        break;
    }

    case MotionEvent.ACTION_POINTER_UP: {
        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) 
                >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
        final int pointerId = ev.getPointerId(pointerIndex);
        if (pointerId == mActivePointerId) {

            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mLastTouchX = ev.getX(newPointerIndex);
            mLastTouchY = ev.getY(newPointerIndex);
            mActivePointerId = ev.getPointerId(newPointerIndex);
        }
        break;
    }
    }

    return true;
}

but I cant get it done. I mean at every action up my method is called. even when the x and y values of both actionup and actiondown are not same. and I think I also need to put some range to the singletap as we touch with our finger on the screen. Can anyone suggest me some ways?

Pang
  • 9,564
  • 146
  • 81
  • 122
Sandeep R
  • 2,284
  • 3
  • 25
  • 51

7 Answers7

42

To detect Single and Double Tap in android I am using the following methods :

class GestureTap extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        Log.i("onDoubleTap :", "" + e.getAction());
        return true;
   }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        Log.i("onSingleTap :", "" + e.getAction());
        return true;
    }
}

Use it in constructor of GestureDetector :

detector = new GestureDetector(this, new GestureTap());

And add the following code in onTouch listener

@Override
public boolean onTouchEvent(MotionEvent event) {

    detector.onTouchEvent(event);

    return true;
}
Bartek Lipinski
  • 30,698
  • 10
  • 94
  • 132
Dhiral Pandya
  • 10,311
  • 4
  • 47
  • 47
  • 2
    +1 Sir thank you for saving my job and my life. You are an angel. Also to add, here is the reference for more supported gesture you can override: https://developer.android.com/training/gestures/detector.html#detect. Look at `Detect Gesture` section. – Neon Warge Sep 14 '16 at 05:56
  • 2
    Thanks so much, this saved me a ton of time! :D – Felix Eder May 16 '18 at 13:09
15

Adding the answer for completeness sake and if anyone else reaches here:

You can use a GestureDetector with an OnTouchListener

final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
         //do something
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            super.onLongPress(e);
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            return super.onDoubleTap(e);
        }
    });

 viewToTouch.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {

            return gestureDetector.onTouchEvent(event);
        }
    });
Droidekas
  • 3,464
  • 2
  • 26
  • 40
9

I also ran into the same problem recently and ended up having to implement a debounce to get it working. It's not ideal, but it's pretty reliable until I can find something better.

View.onClickListener was much more reliable for me, but unfortunately I need the MotionEvent from the OnTouchListener.

Edit: Removed the excess code that would cause it to fail here

class CustomView extends View {

    private static long mDeBounce = 0;

    static OnTouchListener listenerMotionEvent = new OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            if ( Math.abs(mDeBounce - motionEvent.getEventTime()) < 250) {
                //Ignore if it's been less then 250ms since
                //the item was last clicked
                return true;
            }

            int intCurrentY = Math.round(motionEvent.getY());
            int intCurrentX = Math.round(motionEvent.getX());
            int intStartY = motionEvent.getHistorySize() > 0 ? Math.round(motionEvent.getHistoricalY(0)) : intCurrentY;
            int intStartX = motionEvent.getHistorySize() > 0 ? Math.round(motionEvent.getHistoricalX(0)) : intCurrentX;

            if ( (motionEvent.getAction() == MotionEvent.ACTION_UP) && (Math.abs(intCurrentX - intStartX) < 3) && (Math.abs(intCurrentY - intStartY) < 3) ) {
                if ( mDeBounce > motionEvent.getDownTime() ) {
                    //Still got occasional duplicates without this
                    return true;
                }

                //Handle the click

                mDeBounce = motionEvent.getEventTime();
                return true;
            }
            return false;
        }
    };
}
Jon
  • 1,398
  • 9
  • 14
  • 1
    Thank you so much.this worked.best functionality to doubletap in ontouch according to me. you may find some glitches when multitouch but this is best. – Sandeep R Aug 01 '13 at 10:02
  • doesn't work for me, this even overrides my double click method – Casper Nov 09 '13 at 07:37
4

Add GestureDetector.SimpleOnGestureListener for the view and use method onSingleTapConfirmed in this.

This method gets invoked only when Android OS has confirmed the touch on the particular is single tap and not double tap.

You can google for android examples.

Charan
  • 942
  • 1
  • 6
  • 18
  • yeah i tried this..but i am having some other problems with this singletap or doubletap. thanks anyways. – Sandeep R Aug 01 '13 at 08:57
  • onSingleTapConfirmed will be invoked only the tap is single. No confusion here. See the method name itself says it see onSingleTapConfirmed. I dont know what other problems you are referring to, I think you have not handled return values properly. Check it! – Charan Aug 01 '13 at 09:23
  • my problem is not with gesture listener but with my app. my app contains custom views with multitouch pan and zoom in ontouch, which doesnt go good with gesture listener. – Sandeep R Aug 01 '13 at 10:21
4

There is a much simpler and straight forward way. Use MotionEvent.ACTION_DOWN && MotionEvent.ACTION_UP and timing the difference between events.

The full code can be found here. https://stackoverflow.com/a/15799372/3659481

setOnTouchListener(new OnTouchListener() {
private static final int MAX_CLICK_DURATION = 200;
private long startClickTime;

@Override
public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            startClickTime = Calendar.getInstance().getTimeInMillis();
            break;
        }
        case MotionEvent.ACTION_UP: {
            long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
            if(clickDuration < MAX_CLICK_DURATION) {
                //click event has occurred
            }
        }
    }
    return true;
}

}

Community
  • 1
  • 1
0

Think you need not use "equal" operator. Instead of it use approximately value

case MotionEvent.ACTION_DOWN: {
    final int CONST = 5;
    final float x = ev.getX();
    final float y = ev.getY();

    mLastTouchXMax = x+CONST;  //here i get x and y values in action down
    mLastTouchXMin = x-CONST;
    mLastTouchYMax = y+CONST;
    mLastTouchYMin = y-CONST;
    mActivePointerId = ev.getPointerId(0);

    break;
}

And in ACTION_UP check X and Y values between interval.

Sergey Brazhnik
  • 661
  • 4
  • 13
  • i did not understand what to do in action Up.i need to check if x and y values in action up comes under the four values in action down.but how will i do this. help me.thank you. – Sandeep R Aug 01 '13 at 06:51
0
float dX,dY,x,y;     
tv.setOnTouchListener(new View.OnTouchListener() {
                                @Override
                                public boolean onTouch(View view, MotionEvent event) {
                                    switch (event.getAction()) {
                                        case MotionEvent.ACTION_UP:     //Event for On Click
                                            if(x==view.getX() && y==view.getY()){
                                                Toast.makeText(getApplicationContext(),"TextView Clicked",Toast.LENGTH_LONG).show();
                                            }
                                            break;
                                        case MotionEvent.ACTION_DOWN:
                                            x=view.getX();
                                            y=view.getY();
                                            dX = view.getX() - event.getRawX();
                                            dY = view.getY() - event.getRawY();
                                            break;

                                        case MotionEvent.ACTION_MOVE:
                                            view.animate()
                                                    .x(event.getRawX() + dX)
                                                    .y(event.getRawY() + dY)
                                                    .setDuration(0)
                                                    .start();
                                            break;
                                        default:
                                            return false;
                                    }
                                    return true;
                                }
                            });