android - eventos - ¿Cómo distinguir entre movimiento y hacer clic en on TouchEvent()?
implements onclicklistener android studio (10)
Es muy difícil que ocurra una ACTION_DOWN sin que ocurra una ACTION_MOVE. El más leve movimiento de su dedo en la pantalla en un lugar diferente de donde ocurrió el primer toque activará el evento MOVER. Además, creo que un cambio en la presión de los dedos también activará el evento MOVE. Utilizaría una instrucción if en el método Action_Move para tratar de determinar la distancia de movimiento ocurrida desde el movimiento DOWN original. si el movimiento ocurrió fuera de un radio determinado, su acción de MOVIMIENTO ocurrirá. Probablemente no sea la mejor manera, eficiente en cuanto a los recursos, de hacer lo que intentas, pero debería funcionar.
En mi aplicación, necesito manejar eventos de movimiento y clic.
Un clic es una secuencia de una acción ACTION_DOWN, varias acciones ACTION_MOVE y una acción ACTION_UP. En teoría, si obtiene un evento ACTION_DOWN y luego un evento ACTION_UP, significa que el usuario acaba de hacer clic en su Vista.
Pero en la práctica, esta secuencia no funciona en algunos dispositivos. En mi Samsung Galaxy Gio obtengo esas secuencias cuando hago clic en mi Vista: ACTION_DOWN, varias veces ACTION_MOVE, luego ACTION_UP. Es decir, recibo algunas activaciones de OnTouchEvent que no se pueden ver con el código de acción ACTION_MOVE. Nunca (o casi nunca) recibo la secuencia ACTION_DOWN -> ACTION_UP.
Tampoco puedo usar OnClickListener porque no da la posición del clic. Entonces, ¿cómo puedo detectar el evento de clic y diferenciarlo de mover?
El siguiente código resolverá tu problema
@Override public boolean onTouchEvent(MotionEvent event) { switch(event.getAction()) { case(MotionEvent.ACTION_DOWN): x1 = event.getX(); y1 = event.getY(); break; case(MotionEvent.ACTION_UP): { x2 = event.getX(); y2 = event.getY(); dx = x2-x1; dy = y2-y1; if(Math.abs(dx) > Math.abs(dy)) { if(dx>0) move(1); //right else if (dx == 0) move(5); //click else move(2); //left } else { if(dy>0) move(3); // down else if (dy == 0) move(5); //click else move(4); //up } } } return true; }
Para obtener el reconocimiento más optimizado del evento click, tenemos que considerar 2 cosas:
- Diferencia horaria entre ACTION_DOWN y ACTION_UP.
- Diferencia entre x, y cuando el usuario toca y cuando suelta el dedo.
En realidad, combino la lógica dada por Stimsoni y Neethirajan
Así que aquí está mi solución:
view.setOnTouchListener(new OnTouchListener() {
private final int MAX_CLICK_DURATION = 400;
private final int MAX_CLICK_DISTANCE = 5;
private long startClickTime;
private float x1;
private float y1;
private float x2;
private float y2;
private float dx;
private float dy;
@Override
public boolean onTouch(View view, MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
startClickTime = Calendar.getInstance().getTimeInMillis();
x1 = event.getX();
y1 = event.getY();
break;
}
case MotionEvent.ACTION_UP:
{
long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
x2 = event.getX();
y2 = event.getY();
dx = x2-x1;
dy = y2-y1;
if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE)
Log.v("","On Item Clicked:: ");
}
}
return false;
}
});
Aquí hay otra solución que es muy simple y no requiere que se preocupe por el movimiento del dedo. Si está basando un clic simplemente en la distancia movida, ¿cómo puede diferenciar un clic y un clic largo?
Podría poner más detalles en esto e incluir la distancia recorrida, pero todavía tengo que encontrarme con una instancia en la que la distancia que un usuario puede mover en 200 milisegundos debería constituir un movimiento en lugar de un clic.
setOnTouchListener(new OnTouchListener() {
private static final int MAX_CLICK_DURATION = 200;
private long startClickTime;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
startClickTime = Calendar.getInstance().getTimeInMillis();
break;
}
case MotionEvent.ACTION_UP: {
long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
if(clickDuration < MAX_CLICK_DURATION) {
//click event has occurred
}
}
}
return true;
}
});
Si quiere reaccionar solo con un clic, use:
if (event.getAction() == MotionEvent.ACTION_UP) {
}
Agregando a las respuestas anteriores, si desea implementar ambas acciones OnClick y Drag entonces mi código a continuación, chicos. Tomando algo de la ayuda de @Stimsoni:
// assumed all the variables are declared globally;
public boolean onTouch(View view, MotionEvent event) {
int MAX_CLICK_DURATION = 400;
int MAX_CLICK_DISTANCE = 5;
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN: {
long clickDuration1 = Calendar.getInstance().getTimeInMillis() - startClickTime;
startClickTime = Calendar.getInstance().getTimeInMillis();
x1 = event.getX();
y1 = event.getY();
break;
}
case MotionEvent.ACTION_UP:
{
long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
x2 = event.getX();
y2 = event.getY();
dx = x2-x1;
dy = y2-y1;
if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) {
Toast.makeText(getApplicationContext(), "item clicked", Toast.LENGTH_SHORT).show();
Log.d("clicked", "On Item Clicked:: ");
// imageClickAction((ImageView) view,rl);
}
}
case MotionEvent.ACTION_MOVE:
long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
x2 = event.getX();
y2 = event.getY();
dx = x2-x1;
dy = y2-y1;
if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) {
//Toast.makeText(getApplicationContext(), "item clicked", Toast.LENGTH_SHORT).show();
// Log.d("clicked", "On Item Clicked:: ");
// imageClickAction((ImageView) view,rl);
}
else {
ClipData clipData = ClipData.newPlainText("", "");
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
//Toast.makeText(getApplicationContext(), "item dragged", Toast.LENGTH_SHORT).show();
view.startDrag(clipData, shadowBuilder, view, 0);
}
break;
}
return false;
}
Utilizando la respuesta de Gil SH, la mejoré implementando onSingleTapUp()
vez de onSingleTapConfirmed()
. Es mucho más rápido y no hará clic en la vista si se arrastra / mueve.
GestureTap:
public class GestureTap extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
button.performClick();
return true;
}
}
Úselo como:
final GestureDetector gestureDetector = new GestureDetector(getApplicationContext(), new GestureTap());
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_UP:
return true;
case MotionEvent.ACTION_MOVE:
return true;
}
return false;
}
});
Obtuve los mejores resultados teniendo en cuenta:
- Principalmente, la distancia se movió entre los eventos
ACTION_DOWN
yACTION_UP
. Quería especificar la distancia máxima permitida en píxeles independientes de densidad en lugar de píxeles, para admitir mejor las diferentes pantallas. Por ejemplo, 15 DP . - En segundo lugar, la duración entre los eventos. Un segundo parecía bueno como máximo. (Algunas personas "hacen clic" bastante "completamente", es decir, lentamente; aún quiero reconocer eso).
Ejemplo:
/**
* Max allowed duration for a "click", in milliseconds.
*/
private static final int MAX_CLICK_DURATION = 1000;
/**
* Max allowed distance to move during a "click", in DP.
*/
private static final int MAX_CLICK_DISTANCE = 15;
private long pressStartTime;
private float pressedX;
private float pressedY;
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN: {
pressStartTime = System.currentTimeMillis();
pressedX = e.getX();
pressedY = e.getY();
break;
}
case MotionEvent.ACTION_UP: {
long pressDuration = System.currentTimeMillis() - pressStartTime;
if (pressDuration < MAX_CLICK_DURATION && distance(pressedX, pressedY, e.getX(), e.getY()) < MAX_CLICK_DISTANCE) {
// Click event has occurred
}
}
}
}
private static float distance(float x1, float y1, float x2, float y2) {
float dx = x1 - x2;
float dy = y1 - y2;
float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy);
return pxToDp(distanceInPx);
}
private static float pxToDp(float px) {
return px / getResources().getDisplayMetrics().density;
}
La idea aquí es la misma que en la solución de Gem , con estas diferencias:
- Esto calcula la distancia euclidiana real entre los dos puntos.
- Esto usa dp en lugar de px.
Actualización (2015): también echa un vistazo a la versión ajustada de Gabriel de esto .
Siguiendo el ejemplo de Jonik, construí una versión ligeramente más afinada, que no se registra como un clic si mueves el dedo y luego vuelves al punto antes de soltarlo:
Así que aquí está mi solución:
/**
* Max allowed duration for a "click", in milliseconds.
*/
private static final int MAX_CLICK_DURATION = 1000;
/**
* Max allowed distance to move during a "click", in DP.
*/
private static final int MAX_CLICK_DISTANCE = 15;
private long pressStartTime;
private float pressedX;
private float pressedY;
private boolean stayedWithinClickDistance;
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN: {
pressStartTime = System.currentTimeMillis();
pressedX = e.getX();
pressedY = e.getY();
stayedWithinClickDistance = true;
break;
}
case MotionEvent.ACTION_MOVE: {
if (stayedWithinClickDistance && distance(pressedX, pressedY, e.getX(), e.getY()) > MAX_CLICK_DISTANCE) {
stayedWithinClickDistance = false;
}
break;
}
case MotionEvent.ACTION_UP: {
long pressDuration = System.currentTimeMillis() - pressStartTime;
if (pressDuration < MAX_CLICK_DURATION && stayedWithinClickDistance) {
// Click event has occurred
}
}
}
}
private static float distance(float x1, float y1, float x2, float y2) {
float dx = x1 - x2;
float dy = y1 - y2;
float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy);
return pxToDp(distanceInPx);
}
private static float pxToDp(float px) {
return px / getResources().getDisplayMetrics().density;
}
Usa el detector, funciona, y no se elevará en caso de arrastre
Campo:
private GestureDetector mTapDetector;
Inicializar:
mTapDetector = new GestureDetector(context,new GestureTap());
Clase interna:
class GestureTap extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDoubleTap(MotionEvent e) {
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// TODO: handle tap here
return true;
}
}
en contacto:
@Override
public boolean onTouch(View v, MotionEvent event) {
mTapDetector.onTouchEvent(event);
return true;
}
Disfruta :)