40

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); } }); 
1
  • Have you tried returning true from onTouch? Commented Jul 24, 2013 at 10:36

13 Answers 13

77

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); } } 
Sign up to request clarification or add additional context in comments.

5 Comments

It works but this stops the scrolling of recycler view.
@mark922 I'm not sure about recyler view, when I used this code I used it on ListView
Why CLICK_ACTION_THRESHOLD is 200 pixels?
you can use ViewConfiguration.get(context).getScaledTouchSlop() instead of the hardcoded value 200.
should use getRawX() and getRawY() instead of event.getX() and event.getY(). getRawX() and getRawY() that is guaranteed to return absolute coordinates, relative to the device screen. While getX() and getY(), should return you coordinates, relative to the View, that dispatched them.
38

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

2 Comments

You can do this without private variables. duration = event.getEventTime() - event.getDownTime()
This is the correct answer for me. Simple and straight forward.
10

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

3 Comments

So if !isDown && !isUp would mean it's a click? why is that?. I have just tried and it and it didnt work when I clicked.
isDown is depricated. try using ACTION_DOWN and ACTION_UP instead as I have already mentioned above
appologies but it's not about reputation. I don't get any reputation from accepting my own answer. I have upvoted your answer so you'll get some reputation for that. I just think it would be more helpful for the next person looking at this question to get a full answer. if you want you can update your answer with my answer and I will delete mine and accept yours
7

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; } } 

Comments

7

don't try to REINVENT the wheel !

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; } } } 

Comments

4

The answers above mostly memorize the time. However, MotionEvent already has you covered. Here a solution with less overhead. Its written in kotlin but it should still be understandable:

private const val ClickThreshold = 100 override fun onTouch(v: View, event: MotionEvent): Boolean { if(event.action == MotionEvent.ACTION_UP && event.eventTime - event.downTime < ClickThreshold) { v.performClick() return true // If you don't want to do any more actions } // do something in case its not a click return true // or false, whatever you need here } 

This solution is good enough for most applications but is not so good in distinguishing between a click and a very quick swipe.

So, combining this with the answers above that also take the position into account is probably the best one.

Comments

4

Rather than distance / time diff based approaches, You can make use of GestureDetector in combination with setOnTouchListener to achieve this. GestureDetector would detect the click while you can use OnTouchListener for other touch based events, e.g detecting drag.

Here is a sample code for reference:

Class MyCustomView() { fun addClickAndTouchListener() { val gestureDetector = GestureDetector( context, object : GestureDetector.SimpleOnGestureListener() { override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { // Add your onclick logic here return true } } ) setOnTouchListener { view, event -> when { gestureDetector.onTouchEvent(event) -> { // Your onclick logic would be triggered through SimpleOnGestureListener return@setOnTouchListener true } event.action == MotionEvent.ACTION_DOWN -> { // Handle touch event return@setOnTouchListener true } event.action == MotionEvent.ACTION_MOVE -> { // Handle drag return@setOnTouchListener true } event.action == MotionEvent.ACTION_UP -> { // Handle Drag over return@setOnTouchListener true } else -> return@setOnTouchListener false } } } } 

Comments

2

I think combined solution time/position should be better:

 private float initialTouchX; private float initialTouchY; private long lastTouchDown; private static int CLICK_ACTION_THRESHHOLD = 100; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastTouchDown = System.currentTimeMillis(); //get the touch location initialTouchX = event.getRawX(); initialTouchY = event.getRawY(); return true; case MotionEvent.ACTION_UP: int Xdiff = (int) (event.getRawX() - initialTouchX); int Ydiff = (int) (event.getRawY() - initialTouchY); if (System.currentTimeMillis() - lastTouchDown < CLICK_ACTION_THRESHHOLD && (Xdiff < 10 && Ydiff < 10)) { //clicked!!! } return true; } return false; } }); 

Comments

1

I believe you're preventing your view from receiving the touch event this way because your TouchListener intercepts it. You can either

  • Dispatch the event inside your ToucheListener by calling v.onTouchEvent(event)
  • Override instead ViewPager.onTouchEvent(MotionEvent) not to intercept the event

Also, returning true means that you didn't consume the event, and that you're not interrested in following events, and you won't therefore receive following events until the gesture completes (that is, the finger is up again).

Comments

1

you can use OnTouchClickListener

https://github.com/hoffergg/BaseLibrary/blob/master/src/main/java/com/dailycation/base/view/listener/OnTouchClickListener.java

usage:

view.setOnTouchListener(new OnTouchClickListener(new OnTouchClickListener.OnClickListener() { @Override public void onClick(View v) { //perform onClick } },5));

1 Comment

Not that this would not be a valid answer, but the question was already answered with a good answer three years ago.
1
if (event.eventTime - event.downTime < 100 && event.actionMasked == MotionEvent.ACTION_UP) { view.performClick() return false } 

Comments

1

This code will do both touch events and click event.

viewPager.setOnTouchListener(new View.OnTouchListener() { private int initialX; private int initialY; private float initialTouchX; private float initialTouchY; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //remember the initial position. initialX = params.x; initialY = params.y; //get the touch location initialTouchX = event.getRawX(); initialTouchY = event.getRawY(); return true; case MotionEvent.ACTION_UP: int Xdiff = (int) (event.getRawX() - initialTouchX); int Ydiff = (int) (event.getRawY() - initialTouchY); //The check for Xdiff <10 && YDiff< 10 because sometime elements moves a little while clicking. if (Xdiff < 10 && Ydiff < 10) { //So that is click event. } return true; case MotionEvent.ACTION_MOVE: //Calculate the X and Y coordinates of the view. params.x = initialX + (int) (event.getRawX() - initialTouchX); params.y = initialY + (int) (event.getRawY() - initialTouchY); //Update the layout with new X & Y coordinate mWindowManager.updateViewLayout(mFloatingView, params); return true; } return false; } }); 

Here is the source.

Comments

0

I think your problem comes from the line:

v.getParent().requestDisallowInterceptTouchEvent(true); //This cannot be removed 

Take a look to the documentation. Have you tried to remove the line? What is the requeriment for not removing this line?

Take into account, according to the documentation, that if you return true from your onTouchListener, the event is consumed, and if you return false, is propagated, so you could use this to propagate the event.

Also, you should change your code from:

 System.out.println("CLICKED "); 

to:

 Log.d("MyApp", "CLICKED "); 

To get correct output in logcat.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.