How to catch double tap events in Android using OnTouchListener
Asked Answered
I

7

15

I am trying to catch double-tap events using OnTouchListener. I figure I would set a long for motionEvent.ACTION_DOWN, and a different long for a second motionEvent.ACTION_DOWN and measure the time between the two of them and then do something with it. However, I am having a hard time figuring out exactly how to approach this. I am using switch cases to pick up multitouch events, so I'd rather not try and retool this all to implement GestureDetector (and unfortunately it is impossible to implement both ontouchlistener and Gesturedetector simultaneously). Any ideas would help greatly:

i.setOnTouchListener(new OnTouchListener() {

        public boolean onTouch(View v, MotionEvent event) {


                  ImageView i = (ImageView) v;

                  switch (event.getAction() & MotionEvent.ACTION_MASK) {


                  case MotionEvent.ACTION_DOWN:
                      long firstTouch = System.currentTimeMillis();
                     ///how to grab the second action_down????

                     break;
Insular answered 6/9, 2011 at 3:16 Comment(3)
why not use this, developer.android.com/reference/android/view/…Onshore
Because that requires implementing gesturedetector, which I cannot implement simultaneously with ontouchlistenerInsular
This one might help, Though event is onFling, but still a gestureDetector event, #4184882Onshore
G
2

I addressed this problem earlier. It involves using a Handler to wait a certain amount of time to wait for the second click: How can I create a Single Click Event and Double Click Event when the Menu Button is pressed?

Geranial answered 6/9, 2011 at 3:21 Comment(0)
P
15

With the helper class SimpleGestureListener that implements the GestureListener and OnDoubleTapListener you dont need much to do.

yourView.setOnTouchListener(new OnTouchListener() {
private GestureDetector gestureDetector = new GestureDetector(Test.this, new GestureDetector.SimpleOnGestureListener() {
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        Log.d("TEST", "onDoubleTap");
        return super.onDoubleTap(e);
    }
    ... // implement here other callback methods like onFling, onScroll as necessary
});

@Override
public boolean onTouch(View v, MotionEvent event) {
    Log.d("TEST", "Raw event: " + event.getAction() + ", (" + event.getRawX() + ", " + event.getRawY() + ")");
    gestureDetector.onTouchEvent(event);
    return true;
}});
Pucida answered 13/11, 2015 at 14:40 Comment(2)
Awesome response. ThanksSubsidy
The most elegant solution. Thanks!Hurst
H
14

In your class definition:

public class main_activity extends Activity
{
    //variable for counting two successive up-down events
   int clickCount = 0;
    //variable for storing the time of first click
   long startTime;
    //variable for calculating the total time
   long duration;
    //constant for defining the time duration between the click that can be considered as double-tap
   static final int MAX_DURATION = 500;
}

Then in your class body:

OnTouchListener MyOnTouchListener = new OnTouchListener()
{
    @Override
    public boolean onTouch (View v, MotionEvent event)
    {
        switch(event.getAction() & MotionEvent.ACTION_MASK)
        {
        case MotionEvent.ACTION_DOWN:
            startTime = System.currentTimeMillis();
            clickCount++;
            break;
        case MotionEvent.ACTION_UP:
            long time = System.currentTimeMillis() - startTime;
            duration=  duration + time;
            if(clickCount == 2)
            {
                if(duration<= MAX_DURATION)
                {
                    Toast.makeText(captureActivity.this, "double tap",Toast.LENGTH_LONG).show();
                }
                clickCount = 0;
                duration = 0;
                break;             
            }
        }
    return true;    
    }
}

This was adapted from an answer in: DoubleTap in android by https://stackoverflow.com/users/1395802/karn

Housecarl answered 11/4, 2013 at 10:57 Comment(0)
J
8

That's much easyer:

//variable for storing the time of first click
long startTime;
//constant for defining the time duration between the click that can be considered as double-tap
static final int MAX_DURATION = 200;

    if (event.getAction() == MotionEvent.ACTION_UP) {

        startTime = System.currentTimeMillis();             
    }
    else if (event.getAction() == MotionEvent.ACTION_DOWN) {

        if(System.currentTimeMillis() - startTime <= MAX_DURATION)
        {
            //DOUBLE TAP
        }       
    }
Jacobson answered 30/6, 2014 at 16:50 Comment(0)
G
2

I addressed this problem earlier. It involves using a Handler to wait a certain amount of time to wait for the second click: How can I create a Single Click Event and Double Click Event when the Menu Button is pressed?

Geranial answered 6/9, 2011 at 3:21 Comment(0)
M
1

Another approach that supports both single click and double clicks using Kotlin Coroutine:

var lastDown = 0L
var clickCount = 0

view.setOnTouchListener {v, event ->
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            val downTime = System.currentTimeMillis()
            clickCount++
            if (lastDown > 0 && downTime - lastDown < 300 && clickCount == 2) {
                //double clicks happens here
                clickCount = 0
            }
            lastDown = downTime
        }
        MotionEvent.ACTION_UP -> {
            CoroutineScope(Dispatchers.IO).launch {
                //in case clickCount goes beyond than 1, here set it to 1
                val upTime = System.currentTimeMillis()
                if (upTime - lastDown <= 300 && clickCount > 0) {
                    clickCount = 1
                }
                delay(300)
                if (System.currentTimeMillis() - lastDown > 300 && clickCount == 1) {
                    withContext(Dispatchers.Main) {
                        //single click happens here
                    }
                    clickCount = 0
                }
            }
        }
    }
    true
}
Manhandle answered 13/8, 2020 at 11:16 Comment(0)
N
0

Here is my solution.

It was important for me to have a fast and clear separation of 'single tap' and 'double tap'. I tried GestureDetector first but had very bad results. Maybe a result of my nested use of scrollviews - who knows...

I focus on MotionEvent.ACTION_UP and the ID of the tapped element. To keep the first tap alive I use a Handler sending a delayed message (350ms) so the user has some time to place its second tap on the ImageView. If the user placed a second tap on an element with the identical id I take this as double tap, remove the delayed message and rund my custom code for 'double tap'. If the user placed a tap on an element with a different ID I take this as new tap and create another Handler for it.

Class global variables

private int tappedItemId = -1;
Handler myTapHandler;
final Context ctx = this;

Code example

ImageView iv = new ImageView(getApplicationContext());
//[...]
iv.setId(i*1000+n);
iv.setOnTouchListener(new View.OnTouchListener() {

@Override
public boolean onTouch(View v, MotionEvent event) {

   switch (event.getAction()) {

      case MotionEvent.ACTION_UP: {

         //active 'tap handler' for current id?
         if(myTapHandler != null && myTapHandler.hasMessages(v.getId())) {

            // clean up (to avoid single tap msg to be send and handled)
            myTapHandler.removeMessages(tappedItemId);
            tappedItemId = -1;

            //run 'double tap' custom code
            Toast.makeText(ScrollView.this, "double tap on "+v.getId(), Toast.LENGTH_SHORT).show();

            return true;
         } else {
            tappedItemId = v.getId();
            myTapHandler = new Handler(){
               public void handleMessage(Message msg){
                  Toast.makeText(ctx, "single tap on "+ tappedItemId + " msg 'what': " + msg.what, Toast.LENGTH_SHORT).show();
               }
            };

            Message msg = Message.obtain();
            msg.what = tappedItemId;
            msg.obj = new Runnable() {
               public void run() {
                  //clean up
                  tappedItemId = -1;
               }
            };
            myTouchHandler.sendMessageDelayed(msg, 350); //350ms delay (= time to tap twice on the same element)
         }
         break;
      }
   }

   return true;
 }
});
Neolithic answered 19/9, 2014 at 20:25 Comment(0)
C
0
//waiting for 2 taps in MAX_DURATION milliseconds
int clickCount = 0;
//first ACTION_DOWN
long startTime = 0;
//Duratin for (second ACTION_UP - first ACTION_DOWN)
static final int MAX_DURATION = 500;

public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            //if previous startTime was long time ago 
            //(more than MAX_DURATION ago) we reset initial data
            if(System.currentTimeMillis() - startTime > MAX_DURATION) {
                startTime = System.currentTimeMillis();
                clickCount = 0;
            }
            break;
        case MotionEvent.ACTION_UP:
            //if ACTION_UP happens in allowed time MAX_DURATION we increase clickCount
            if(System.currentTimeMillis() - startTime <= MAX_DURATION) {
                clickCount++;
                //if two ACTION_UP happens in allowed time MAX_DURATION
                if(clickCount == 2) {
                    //double tap happens here
                }                                        
            }
            break;
    }
    return true;
}
Coach answered 15/8 at 10:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.