47

I am trying to handle touch events and click events on a button. I do the following:

button.setOnClickListener(clickListener); button.setOnTouchListener(touchListener); 

When any one listener is registered things work fine but when I try to use them both only the touch events are fired. Any workaround? What am I doing wrong?

13 Answers 13

78

Its a little tricky.

If you set onTouchListener you need to return true in ACTION_DOWN, to tell the system that I have consumed the event and it won't trickle down to other listeners.

But then OnClickListener won't be fired.

So you might think, I will just do my thing there and return false so I can receive clicks too. If you do so, it will work, but you won't be subscribed to other upcoming touch events (ACTION_MOVE, ACTION_UP) Therefore, the only option is to return true there, but then you won't receive any click events as we said previously.

So you need to perform the click manually in the ACTION_UP with view.performClick()

This will work.

Sign up to request clarification or add additional context in comments.

4 Comments

The key part of this is "you need to perform the click manually in the ACTION_UP", thank you sir
This is really the best answer. In my ACTION_MOVE, I begin intercepting touch events if a certain deltaX threshold is met. If I get to ACTION_UP and that deltaX threshold was never met, I fire listView.performClick(), otherwise, I do my swipe actions.
This obviously should be the answer.
Great answer. Keep it up
42

There is a subtle, yet very important difference between the ClickListener and the TouchListener. The TouchListener is executed before the view can respond to the event. The ClickListener will receive its event only after the view has handled it.

So when you touch your screen, the TouchListener is executed first and when you return true for your event, the ClickListener will never get it. But if you press the trackball of your device, the ClickListener should be fired because the TouchListener will not respond to it.

Comments

21

Thanks to @urSus for great answer
But in that case every touch will perform click, Even ACTION_MOVE
Assuming you want to separate move event and click event you can use a little trick
define a boolean field and use like this:

 @Override public boolean onTouch(View view, MotionEvent motionEvent) { switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: shouldClick = true; . . break; case MotionEvent.ACTION_UP: if (shouldClick) view.performClick(); break; case MotionEvent.ACTION_POINTER_DOWN: break; case MotionEvent.ACTION_POINTER_UP: break; case MotionEvent.ACTION_MOVE: //Do your stuff shouldClick = false; break; } rootLayout.invalidate(); return true; } 

5 Comments

Great answer. Keep it up
perform click is the view method ? or a custom method where we have to perform click
@user3475052 performClick() is every view and its subclasses method
It works, if return true; in MotionEvent.ACTION_DOWN branch, remove MotionEvent.ACTION_MOVE branch, get movement difference in MotionEvent.ACTION_UP branch. See stackoverflow.com/questions/17831395/…
really this answer is best in all similar question
6

You should return false in your OnTouchListener then your OnClickListener will be also handled.

Comments

5

I suppose you're returning true in your OnTouchListener? That will consume the event so it won't be sent down for any further processing.

On a side note - what's the point of having both a click and touch listener?

2 Comments

The client wants a sound to be played when the button is pressed and released, I do it in the 'ACTION_UP' and 'ACTION_DOWN' events. And the click event does the logic. Now I'll have to handle the logic in the touch event itself.
A button that can be dragged around, then clicked when finished?
4

This is an example of TouchListener that doesn't shadow ClickListener.

import android.graphics.PointF import android.view.MotionEvent import android.view.MotionEvent.* import android.view.View import kotlin.math.abs object TouchAndClickListener : View.OnTouchListener { /** Those are factors you can change as you prefer */ private const val touchMoveFactor = 10 private const val touchTimeFactor = 200 private var actionDownPoint = PointF(0f, 0f) private var previousPoint = PointF(0f, 0f) private var touchDownTime = 0L override fun onTouch(v: View, event: MotionEvent) = when (event.action) { ACTION_DOWN -> PointF(event.x, event.y).let { actionDownPoint = it // Store it to compare position when ACTION_UP previousPoint = it // Store it to compare position when ACTION_MOVE touchDownTime = now() // Store it to compare time when ACTION_UP /* Do other stuff related to ACTION_DOWN you may whant here */ true } ACTION_UP -> PointF(event.x, event.y).let { val isTouchDuration = now() - touchDownTime < touchTimeFactor // short time should mean this is a click val isTouchLength = abs(it.x - actionDownPoint.x) + abs(it.y - actionDownPoint.y) < touchMoveFactor // short length should mean this is a click val shouldClick = isTouchLength && isTouchDuration // Check both if (shouldClick) yourView.performClick() //Previously define setOnClickListener{ } on yourView, then performClick() will call it /* Do other stuff related to ACTION_UP you may whant here */ true } ACTION_MOVE -> PointF(event.x, event.y).let { /* Do other stuff related to ACTION_MOVE you may whant here */ previousPoint = it true } else -> false // Nothing particular with other event } private fun now() = System.currentTimeMillis() } 

1 Comment

Great answer but please it should have been in java not kotlin
4

Thanks to @Nicolas Duponchel this is how i achieved both onClick and onTouch events

 //Define these globally e.g in your MainActivity class private short touchMoveFactor = 10; private short touchTimeFactor = 200; private PointF actionDownPoint = new PointF(0f, 0f); private long touchDownTime = 0L; @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: { actionDownPoint.x = event.getX(); actionDownPoint.y = event.getY(); touchDownTime = System.currentTimeMillis(); break; } case MotionEvent.ACTION_UP: { //on touch released, check if the finger was still close to the point he/she clicked boolean isTouchLength = (Math.abs(event.getX() - actionDownPoint.x)+ Math.abs(event.getY() - actionDownPoint.y)) < touchMoveFactor; boolean isClickTime = System.currentTimeMillis() - touchDownTime < touchTimeFactor; //if it's been more than 200ms since user first touched and, the finger was close to the same place when released, consider it a click event //Please note that this is a workaround :D if (isTouchLength && isClickTime){ //call this method on the view, e.g ivPic.performClick(); performClick();} break; } } } 

Comments

3

All above answer is said that we can not handle both setOnTouchListener and setOnClickListener.
However, I see we can handle both by return false in setOnTouchListener

Example

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button = findViewById(R.id.button) button.setOnClickListener { Log.i("TAG", "onClick") } button.setOnTouchListener { v, event -> Log.i("TAG", "onTouch " + event.action) false } } 

When I click at Button, logcat will display like

I/TAG: onTouch 0 I/TAG: onTouch 1 I/TAG: onClick 

1 Comment

returning false from onTouch is equal to not setting onTouchListener at all.
1

In ACTION_UP perform onClick manually using condition

boolean shouldClick = event.eventTime - event.downTime <= 200 // compares the time with some threshold 

So, try within MotionEvent.ACTION_UP

if(event.eventTime - event.downTime <= 200) { // case or when statement of action Touch listener view.performClick(); } 

Comments

0
button.setOnTouchListener(this); 

Implement interface and the code here:

@Override public boolean onTouch(View view, MotionEvent motionEvent) { switch (view.getId()) { case R.id.send: switch(motionEvent.getAction()){ case MotionEvent.ACTION_DOWN: //when the user has pressed the button //do the needful here break; case MotionEvent.ACTION_UP: //when the user releases the button //do the needful here break; } break; } return false; } 

Comments

0

To make both events possible in gridview,only by making return of touch listener"false" as follows,this worked for me.

**GridView myView = findViewById(R.id.grid_view); myView.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { // ... Respond to touch events return false; } });** 

in this way both events can be achieved

Comments

0
## Exact working solution for both click action and touch listener(dragging) ## private int initialX; private int initialY; private float initialTouchX; private float initialTouchY; private float CLICK_ACTION_THRESHOLD = 0.5f; private float startX; private float startY; @Override public boolean onTouch(View view, MotionEvent event) { switch (view.getId()) { case R.id.chat_head_profile_iv: switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //remember the initial position. initialX = params.x; initialY = params.y; startX = event.getX(); startY = event.getY(); //get the touch location initialTouchX = event.getRawX(); initialTouchY = event.getRawY(); return true; case MotionEvent.ACTION_UP: float endX = event.getX(); float endY = event.getY(); if (shouldClickActionWork(startX, endX, startY, endY)) { openScreen();// WE HAVE A CLICK!! } 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(mChatHeadView, params); return true; } break; } return true; } private boolean shouldClickActionWork(float startX, float endX, float startY, float endY) { float differenceX = Math.abs(startX - endX); float differenceY = Math.abs(startY - endY); if ((CLICK_ACTION_THRESHOLD > differenceX) && (CLICK_ACTION_THRESHOLD > differenceY)) return true; else return false; } 

1 Comment

What is params ?
0

I knows its too late, but if someone is struggling with this for a clean solution, here it is.

These are used for measuring the time between touching and removing the finger.

 private long clickTime = 0; public static final long CLICK_TIMEOUT = 200; // 200ms 

This my onTouchListner. Works like a charm

 private final View.OnTouchListener onTouchListener = (v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { clickTime = System.currentTimeMillis(); return true; } else if(event.getAction() == MotionEvent.ACTION_UP) { if(System.currentTimeMillis()-clickTime < Constants.CLICK_TIMEOUT) { Toast.makeText(getContext(), "clicked", Toast.LENGTH_SHORT).show(); return true; } return false; } else if(event.getAction() == MotionEvent.ACTION_MOVE){ if(System.currentTimeMillis()-clickTime > Constants.CLICK_TIMEOUT) { ClipData data = ClipData.newPlainText("" , ""); View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(v); v.startDrag(data , shadowBuilder , v , 0); return false; } return false; } return false; }; 

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.