简体   繁体   中英

Android - how to detect a touch on screen is a "scroll" touch?

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> .

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

});

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.

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

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

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

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

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 😉

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM