How can I detect a click in an onTouch listener?

I have a ViewPager inside a ScrollView. I need to be able to scroll horizontally as well as vertically. In order to achieve this had to disable the vertical scrolling whenever my ViewPager is touched (v.getParent().requestDisallowInterceptTouchEvent(true);), so that it can be scrolled horizontally.

But at the same time I need to be able to click the viewPager to open it in full screen mode.

The problem is that onTouch gets called before onClick and my OnClick is never called.

How can I implement both on touch an onClick?

viewPager.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        System.out.println("TOUCHED ");
        if(event.getAction() == MotionEvent.???){
            //open fullscreen activity
        }
        v.getParent().requestDisallowInterceptTouchEvent(true); //This cannot be removed
        return false;
    }
});

viewPager.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        System.out.println("CLICKED ");
        Intent fullPhotoIntent = new Intent(context, FullPhotoActivity.class);
        fullPhotoIntent.putStringArrayListExtra("imageUrls", imageUrls);
        startActivity(fullPhotoIntent);
    }
});

Masoud Dadashi's answer helped me figure it out.

here is how it looks in the end.

viewPager.setOnTouchListener(new OnTouchListener() {
    private int CLICK_ACTION_THRESHOLD = 200;
    private float startX;
    private float startY;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            startX = event.getX();
            startY = event.getY();
            break;
        case MotionEvent.ACTION_UP: 
            float endX = event.getX();
            float endY = event.getY();
            if (isAClick(startX, endX, startY, endY)) { 
                launchFullPhotoActivity(imageUrls);// WE HAVE A CLICK!!
            }
            break;
        }
        v.getParent().requestDisallowInterceptTouchEvent(true); //specific to my project
        return false; //specific to my project
    }

    private boolean isAClick(float startX, float endX, float startY, float endY) {
        float differenceX = Math.abs(startX - endX);
        float differenceY = Math.abs(startY - endY);
        return !(differenceX > CLICK_ACTION_THRESHOLD/* =5 */ || differenceY > CLICK_ACTION_THRESHOLD);
    } 
}

I did something really simple by checking the time the user touches the screen.

private static int CLICK_THRESHOLD = 100;

@Override
public boolean onTouch(View v, MotionEvent event) {
    long duration = event.getEventTime() - event.getDownTime();

    if (event.getAction() == MotionEvent.ACTION_UP && duration < CLICK_THRESHOLD) {
        Log.w("bla", "you clicked!");
    }

    return false;
}

Also worth noting that GestureDetector has something like this built-in. Look at onSingleTapUp


Developing both is the wrong idea. when user may do different things by touching the screen understanding user purpose is a little bit nifty and you need to develop a piece of code for it.

Two solutions:

1- (the better idea) in your onTouch event check if there is a motion. You can do it by checking if there is any movement using:

ACTION_UP
ACTION_DOWN
ACTION_MOVE

do it like this

if(event.getAction() != MotionEvent.ACTION_MOVE)

you can even check the distance of the movement of user finger on screen to make sure a movement happened rather than an accidental move while clicking. do it like this:

switch(event.getAction())
 {
     case MotionEvent.ACTION_DOWN:
              if(isDown == false)
              {
                     startX = event.getX();
                     startY = event.getY();
                     isDown = true;
              }
              Break;
        case MotionEvent.ACTION_UP
              {
                     endX = event.getX();
                     endY = event.getY();
                     break;
          }
}

consider it a click if none of the above happened and do what you wanna do with click.

2) if rimes with your UI, create a button or image button or anything for full screening and set an onClick for it.

Good luck


You might need to differentiate between the user clicking and long-clicking. Otherwise, you'll detect both as the same thing. I did this to make that possible:

@Override
public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
        startX = event.getX();
        startY = event.getY();

        bClick = true;
        tmrClick = new Timer();
        tmrClick.schedule(new TimerTask() {
            public void run() {
                if (bClick == true) {
                    bClick = false;
                    Log.d(LOG_TAG, "Hey, a long press event!");
                    //Handle the longpress event.
                }
            }
        }, 500); //500ms is the standard longpress response time. Adjust as you see fit.

        return true;
    case MotionEvent.ACTION_UP:
        endX = event.getX();
        endY = event.getY();

        diffX = Math.abs(startX - endX);
        diffY = Math.abs(startY - endY);

        if (diffX <= 5 && diffY <= 5 && bClick == true) {
            Log.d(LOG_TAG, "A click event!");
            bClick = false;
        }
        return true;
    default:
        return false;
    }
}

Elegant way to do it

public class CustomView extends View {

    private GestureDetectorCompat mDetector;

    public CustomView(Context context) {
        super(context);
        mDetector = new GestureDetectorCompat(context, new MyGestureListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        return this.mDetector.onTouchEvent(event);
    }

    class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onDown(MotionEvent e) {return true;}

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            //...... click detected !
            return false;
        }
    }
}