Detectar una pulsación larga en Android

9 minutos de lectura

avatar de usuario
Jack

actualmente estoy usando onTouchEvent(MotionEvent event) { } para detectar cuándo el usuario presiona mi glSurfaceView, hay una manera de detectar cuándo se realiza un clic prolongado.

Supongo que si no puedo encontrar mucho en los documentos de desarrollo, será algún tipo de método alternativo. Algo así como registrarse ACTION_DOWN y viendo cuanto tiempo pasa antes ACTION_UP.

¿Cómo detectas pulsaciones largas en Android usando opengl-es?

avatar de usuario
mcuadrado

Detector de gestos es la mejor solución.

Aquí hay una alternativa interesante. En onTouchEvent en cada ACCIÓN_ABAJO programe un Runnable para que se ejecute en 1 segundo. En cada ACCIÓN_ARRIBA o ACCIÓN_MOVER, cancela Runnable programado. Si la cancelación ocurre en menos de 1 segundo desde ACCIÓN_ABAJO evento, Runnable no se ejecutará.

final Handler handler = new Handler(); 
Runnable mLongPressed = new Runnable() { 
    public void run() { 
        Log.i("", "Long press!");
    }   
};

@Override
public boolean onTouchEvent(MotionEvent event, MapView mapView){
    if(event.getAction() == MotionEvent.ACTION_DOWN)
        handler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout());
    if((event.getAction() == MotionEvent.ACTION_MOVE)||(event.getAction() == MotionEvent.ACTION_UP))
        handler.removeCallbacks(mLongPressed);
    return super.onTouchEvent(event, mapView);
}

  • Puede obtener el tiempo de espera del sistema para la pulsación larga llamando android.view.ViewConfiguration.getLongPressTimeout().

    – rekire

    17/03/2014 a las 13:30

  • No tengo idea de por qué, pero ambas soluciones no funcionan para mí.

    – Eido95

    19 dic 2016 a las 20:07


  • ACTION_CANCEL también debe ser manejado

    – Kirill Vashilo

    30 de enero de 2017 a las 7:14

  • Gracias, @MSquare. Esta es una gran idea. Tenga en cuenta que para el dispositivo que estoy usando, puedo eliminar la devolución de llamada solo en respuesta a ACTION_UP; esto se debe a que es muy sensible al movimiento y no puedo evitar los eventos ACTION_MOVE. Tenga en cuenta también que su enfoque proporciona una forma natural de implementar una acción repetitiva, llamando a Runnable de forma recursiva.

    – stevehs17

    29 de enero de 2019 a las 1:17

  • @stevehs Debe probar la pendiente táctil.

    usuario9599745

    5 de febrero de 2020 a las 1:38

Prueba esto:

final GestureDetector gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
    public void onLongPress(MotionEvent e) {
        Log.e("", "Longpress detected");
    }
});

public boolean onTouchEvent(MotionEvent event) {
    return gestureDetector.onTouchEvent(event);
};

  • ¿Admite Gesture Detector también los clics estándar?

    – Jack

    27/10/2011 a las 20:03

  • Nota: si ya tiene un OnClickListener, es mucho más fácil agregar un OnLongClickListener que usar un GestureRecognizer (hay algunos problemas específicos de GR que puede evitar). cf stackoverflow.com/a/4402854/153422

    – Adán

    20 de abril de 2013 a las 17:42

  • Tenga en cuenta que debe proporcionar un contexto sobre la creación de GestureDetector como primer argumento. El ejemplo está en desuso.

    – Antzi

    9 oct 2013 a las 12:19

  • Para evitar subclasificar una vista, use Ver#setOnTouchListener(). Consulte “Captura de eventos táctiles para una sola vista” en Detección de gestos comunes.

    – Pang

    10 de abril de 2014 a las 5:12

  • @Modge solo dos de GestureDetector los constructores están en desuso; puede usar el resto de los constructores.

    – Eido95

    21 de diciembre de 2016 a las 18:47

Tengo un código que detecta un clic, un clic largo y movimiento. Es bastante una combinación de la respuesta dada anteriormente y los cambios que hice al mirar cada página de documentación.

//Declare this flag globally
boolean goneFlag = false;

//Put this into the class
final Handler handler = new Handler(); 
    Runnable mLongPressed = new Runnable() { 
        public void run() { 
            goneFlag = true;
            //Code for long click
        }   
    };

//onTouch code
@Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {    
        case MotionEvent.ACTION_DOWN:
            handler.postDelayed(mLongPressed, 1000);
            //This is where my code for movement is initialized to get original location.
            break;
        case MotionEvent.ACTION_UP:
            handler.removeCallbacks(mLongPressed);
            if(Math.abs(event.getRawX() - initialTouchX) <= 2 && !goneFlag) {
                //Code for single click
                return false;
            }
            break;
        case MotionEvent.ACTION_MOVE:
            handler.removeCallbacks(mLongPressed);
            //Code for movement here. This may include using a window manager to update the view
            break;
        }
        return true;
    }

Confirmo que funciona, ya que lo he usado en mi propia aplicación.

  • Fui con este, pero tuve que configurar goneFlag para que fuera falso en ACTION_DOWN antes de llamar al handler.postDelayed; de lo contrario, cada clic después del primer clic largo se trata como un clic largo.

    – Jim Jimson

    14 de abril de 2019 a las 4:50

  • Lo hace initialTouchX se define automáticamente en algún lugar? Estoy confundido.

    – Divya Mamgai

    14 de julio de 2020 a las 1:38

he creado un retazo – inspirado en la fuente de View real – que detecta de manera confiable los clics/presiones prolongados con un retraso personalizado. Pero está en Kotlin:

val LONG_PRESS_DELAY = 500

val handler = Handler()
var boundaries: Rect? = null

var onTap = Runnable {
    handler.postDelayed(onLongPress, LONG_PRESS_DELAY - ViewConfiguration.getTapTimeout().toLong())
}

var onLongPress = Runnable {

    // Long Press
}

override fun onTouch(view: View, event: MotionEvent): Boolean {
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            boundaries = Rect(view.left, view.top, view.right, view.bottom)
            handler.postDelayed(onTap, ViewConfiguration.getTapTimeout().toLong())
        }
        MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
            handler.removeCallbacks(onLongPress)
            handler.removeCallbacks(onTap)
        }
        MotionEvent.ACTION_MOVE -> {
            if (!boundaries!!.contains(view.left + event.x.toInt(), view.top + event.y.toInt())) {
                handler.removeCallbacks(onLongPress)
                handler.removeCallbacks(onTap)
            }
        }
    }
    return true
}

Cuando te refieres a las pulsaciones de los usuarios, ¿te refieres a un clic? Un clic es cuando el usuario presiona hacia abajo y luego levanta inmediatamente el dedo. Por lo tanto, abarca dos eventos onTouch. Debe guardar el uso de onTouchEvent para las cosas que suceden en el toque inicial o después del lanzamiento.

Por lo tanto, debería usar onClickListener si se trata de un clic.

Su respuesta es análoga: Use onLongClickListener.

  • ¿Puedo usar onClickListener solo con GLSurfaceView? Tenía la impresión de que solo podía usar onClickListener con widgets de interfaz de usuario como botones. Aunque podría estar equivocado.

    – Jack

    27/10/2011 a las 18:50

  • Ah, lo veo genial, pero ¿hay alguna forma de obtener las coordenadas del clic a través de ClickListener?

    – Jack

    27/10/2011 a las 19:24

  • onClickListener no parece funcionar para GLSurfaceView aunque lo tengo envuelto en un FrameLayout. Intenté configurar el oyente en la vista de superficie y el diseño del marco, pero no funcionó =/

    – Jack

    27/10/2011 a las 19:39

  • Esta pregunta parece ser específica de GLSurfaceView; sin embargo, en general, para otras Vistas, OnLongClickListener es el camino a seguir.

    – Sarang

    16 dic 2014 a las 19:55

avatar de usuario
EpicPandaFuerza

La solución de MSquare funciona solo si sostiene un píxel específico, pero esa es una expectativa poco razonable para un usuario final a menos que use un mouse (que no es así, usa los dedos).

Así que agregué un pequeño umbral para la distancia entre la acción ABAJO y ARRIBA en caso de que hubiera una acción MOVER en el medio.

final Handler longPressHandler = new Handler();
Runnable longPressedRunnable = new Runnable() {
    public void run() {
        Log.e(TAG, "Long press detected in long press Handler!");
        isLongPressHandlerActivated = true;
    }
};

private boolean isLongPressHandlerActivated = false;

private boolean isActionMoveEventStored = false;
private float lastActionMoveEventBeforeUpX;
private float lastActionMoveEventBeforeUpY;

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN) {
        longPressHandler.postDelayed(longPressedRunnable, 1000);
    }
    if(event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_HOVER_MOVE) {
        if(!isActionMoveEventStored) {
            isActionMoveEventStored = true;
            lastActionMoveEventBeforeUpX = event.getX();
            lastActionMoveEventBeforeUpY = event.getY();
        } else {
            float currentX = event.getX();
            float currentY = event.getY();
            float firstX = lastActionMoveEventBeforeUpX;
            float firstY = lastActionMoveEventBeforeUpY;
            double distance = Math.sqrt(
                    (currentY - firstY) * (currentY - firstY) + ((currentX - firstX) * (currentX - firstX)));
            if(distance > 20) {
                longPressHandler.removeCallbacks(longPressedRunnable);
            }
        }
    }
    if(event.getAction() == MotionEvent.ACTION_UP) {
        isActionMoveEventStored = false;
        longPressHandler.removeCallbacks(longPressedRunnable);
        if(isLongPressHandlerActivated) {
            Log.d(TAG, "Long Press detected; halting propagation of motion event");
            isLongPressHandlerActivated = false;
            return false;
        }
    }
    return super.dispatchTouchEvent(event);
}

  • ¿Puedo usar onClickListener solo con GLSurfaceView? Tenía la impresión de que solo podía usar onClickListener con widgets de interfaz de usuario como botones. Aunque podría estar equivocado.

    – Jack

    27/10/2011 a las 18:50

  • Ah, lo veo genial, pero ¿hay alguna forma de obtener las coordenadas del clic a través de ClickListener?

    – Jack

    27/10/2011 a las 19:24

  • onClickListener no parece funcionar para GLSurfaceView aunque lo tengo envuelto en un FrameLayout. Intenté configurar el oyente en la vista de superficie y el diseño del marco, pero no funcionó =/

    – Jack

    27/10/2011 a las 19:39

  • Esta pregunta parece ser específica de GLSurfaceView; sin embargo, en general, para otras Vistas, OnLongClickListener es el camino a seguir.

    – Sarang

    16 dic 2014 a las 19:55

La idea es crear un Runnable para ejecutar un clic largo en el futuro, pero esta ejecución puede cancelarse debido a un clic o movimiento.

También necesita saber cuándo se consumió un clic largo y cuándo se canceló porque el dedo se movió demasiado. Usamos initialTouchX & initialTouchY para comprobar si el usuario sale de un área cuadrada de 10 píxeles, 5 de cada lado.

Aquí está mi código completo para delegar Clic y clic largo desde Cell en ListView para Activity con OnTouchListener:

    ClickDelegate delegate; 
    boolean goneFlag = false;
    float initialTouchX;
    float initialTouchY;
    final Handler handler = new Handler();
    Runnable mLongPressed = new Runnable() {
        public void run() {
            Log.i("TOUCH_EVENT", "Long press!");
            if (delegate != null) {
                goneFlag = delegate.onItemLongClick(index);
            } else {
                goneFlag = true;
            }
        }
    };

    @OnTouch({R.id.layout})
    public boolean onTouch (View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
                handler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout());
                initialTouchX = motionEvent.getRawX();
                initialTouchY = motionEvent.getRawY();
                return true;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_CANCEL:
                if (Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
                    handler.removeCallbacks(mLongPressed);
                    return true;
                }
                return false;
            case MotionEvent.ACTION_UP:
                handler.removeCallbacks(mLongPressed);
                if (goneFlag || Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
                    goneFlag = false;
                    return true;
                }
                break;
        }
        Log.i("TOUCH_EVENT", "Short press!");
        if (delegate != null) {
            if (delegate.onItemClick(index)) {
                return false;
            }
        }
        return false;
    }

ClickDelegatees un interface para enviar eventos de clic a la clase de controlador como un Activity

    public interface ClickDelegate {
        boolean onItemClick(int position);
        boolean onItemLongClick(int position);
    }

Y todo lo que necesitas es implementarlo en tu Activity o padre Viewsi necesita delegar el comportamiento:

public class MyActivity extends Activity implements ClickDelegate {

    //code...
    //in some place of you code like onCreate, 
    //you need to set the delegate like this:
    SomeArrayAdapter.delegate = this;
    //or:
    SomeViewHolder.delegate = this;
    //or:
    SomeCustomView.delegate = this;

    @Override
    public boolean onItemClick(int position) {
        Object obj = list.get(position);
        if (obj) {
            return true; //if you handle click
        } else {
            return false; //if not, it could be another event
        }
    }

    @Override
    public boolean onItemLongClick(int position) {
        Object obj = list.get(position);
        if (obj) {
            return true; //if you handle long click
        } else {
            return false; //if not, it's a click
        }
    }
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad