9

I am creating an android app in Java in which I have a lot of <TextView> around the screen, all of them with onTouchListeners defined. They are wrapped in a <ScrollView> because they occupy more space than available in the screen.

My problem is: when I scroll the app, up/down, by touching at the screen and moving my finger up/down, the scroll works as expected but the onTouchListener of the touched <TextView> is also fired (which is probably expected as well) - I don't want that to happen though. I want the onTouchListener to be ignored when I'm touching the screen to scroll it.

How can I accomplish this? I don't want my function to run when the user is scrolling and "accidentally" fires the onTouchListener on a certain <TextView>.

2

7 Answers 7

22

After searching more, I found this solution by Stimsoni. The idea is to check if the time between the ACTION_DOWN and ACTION_UP events is lower or higher than the value given by ViewConfiguration.getTapTimeout().

From the documentation:

[Returns] the duration in milliseconds we will wait to see if a touch event is a tap or a scroll. If the user does not move within this interval, it is considered to be a tap.

Code:

view.setOnTouchListener(new OnTouchListener() { private long startClickTime; @Override public boolean onTouch(View view, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { startClickTime = System.currentTimeMillis(); } else if (event.getAction() == MotionEvent.ACTION_UP) { if (System.currentTimeMillis() - startClickTime < ViewConfiguration.getTapTimeout()) { // Touch was a simple tap. Do whatever. } else { // Touch was a not a simple tap. } } return true; } }); 
Sign up to request clarification or add additional context in comments.

Comments

2

I had the same problem as you, and I solved it with ACTION_CANCEL.

motionEvent.getActionMasked() is equal to ACTION_CANCEL when an action perceived previously (like ACTION_DOWN in your case) is "canceled" now by other gestures like scrolling, etc. your code may be like this:

view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent e) { if (e.getActionMasked() == MotionEvent.ACTION_DOWN) { // perceive a touch action. } else if(e.getActionMasked() == MotionEvent.ACTION_UP || e.getActionMasked() == MotionEvent.ACTION_CANCEL) { // ignore the perceived action. } } 

I hope this helps.

Comments

1

I had a similar problem but with one TextView, search led me here. The text-content potentially takes up more space than available on screen. Simple working example: bpmcounter-android (Kotlin)

class MainActivity : AppCompatActivity() { inner class GestureTap : GestureDetector.SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent?): Boolean { // Do your buttonClick stuff here. Any scrolling action will be ignored return true } } @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView = findViewById<TextView>(R.id.textView) textView.movementMethod = ScrollingMovementMethod() val gestureDetector = GestureDetector(this, GestureTap()) textView.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) } } } 

Comments

1

1 METHOD:

I figured out that the best method to do this is detecting the first touch saving the points x and y and then confront it with the second touch. If the distance between the first click and the second one is quite close (I put 10% as an approximation) then the touch a simple click otherwise is a scrolling movement.

 /** * determine whether two numbers are "approximately equal" by seeing if they * are within a certain "tolerance percentage," with `tolerancePercentage` given * as a percentage (such as 10.0 meaning "10%"). * * @param tolerancePercentage 1 = 1%, 2.5 = 2.5%, etc. */ fun approximatelyEqual(desiredValue: Float, actualValue: Float, tolerancePercentage: Float): Boolean { val diff = Math.abs(desiredValue - actualValue) // 1000 - 950 = 50 val tolerance = tolerancePercentage / 100 * desiredValue // 20/100*1000 = 200 return diff < tolerance // 50<200 = true } var xPoint = 0f var yPoint = 0f @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { when(event.action) { MotionEvent.ACTION_DOWN -> { xPoint = event.x yPoint = event.y return true } MotionEvent.ACTION_UP -> { if (!approximatelyEqual(xPoint, event.x, 10f) || !approximatelyEqual(yPoint, event.y, 10f)) { //scrolling } else { //simple click } } } return false } 

2 METHOD:

Another way to do the same thing is by using the GestureDetector class:

 interface GestureInterface { fun setOnScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float) fun onClick(e: MotionEvent) } class MyGestureDetector(val gestureInterfacePar: GestureInterface) : SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent): Boolean { gestureInterfacePar.onClick(e) return false } override fun onLongPress(e: MotionEvent) {} override fun onDoubleTap(e: MotionEvent): Boolean { return false } override fun onDoubleTapEvent(e: MotionEvent): Boolean { return false } override fun onSingleTapConfirmed(e: MotionEvent): Boolean { return false } override fun onShowPress(e: MotionEvent) { } override fun onDown(e: MotionEvent): Boolean { return true } override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean { gestureInterfacePar.setOnScroll(e1, e2, distanceX, distanceY) return false } override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { return super.onFling(e1, e2, velocityX, velocityY) } } 

and finally, bind it with your view:

val mGestureDetector = GestureDetector(context, MyGestureDetector(object : GestureInterface { override fun setOnScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float) { //handle the scroll } override fun onClick(e: MotionEvent) { //handle the single click } })) view.setOnTouchListener(OnTouchListener { v, event -> mGestureDetector.onTouchEvent(event) }) 

2 Comments

I guess this makes sense too. Is there a reason the currently accepted answer is not good enough for you though?
Side note: you should not use percentages in your first method, but fixed tolerance value. Else, the tolerance would be 10px if x=100, 100px if x=1000, and 1px if x=10. You don't want the left side of the screen to be less tolerant than the right side of the screen :)
0

You can identify moving action like this:

view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if(event.getAction() == MotionEvent.ACTION_MOVE) { } return false; } }); 

1 Comment

Thanks; although didn't didn't really solve my problem, it gave me ideas to search more and I was able to solve the problem. Thank you very much.
0

Worked for me :

View.OnTouchListener() { @Override public boolean onTouch(View v,MotionEvent event) { if(event.getAction()!=MotionEvent.ACTION_DOWN) { // Before touch } else { // When touched } return true }); 

Comments

0

You dont need to go for such cómplicated method for capturing a "click" event. Just for this method :-

//Inside on touch listener of course :-

KOTLIN :-

if(event.action == MotionEvent.ACTION_UP && event.action != MotionEvent.ACTION_MOVE) { // Click has been made... // Some code } 

JAVA :- Just replace event.action with event.getAction()

This works for me 😉

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.