Dans mon application, je dois gérer les événements de déplacement et de clic.
Un clic est une séquence d'une action ACTION_DOWN, de plusieurs actions ACTION_MOVE et d'une action ACTION_UP. En théorie, si vous obtenez un événement ACTION_DOWN, puis un événement ACTION_UP, cela signifie que l'utilisateur vient de cliquer sur votre vue.
Mais en pratique, cette séquence ne fonctionne pas sur certains appareils. Sur mon Samsung Galaxy Gio, de telles séquences apparaissent lorsque je clique simplement sur ma vue: ACTION_DOWN, plusieurs fois ACTION_MOVE, puis ACTION_UP. C'est à dire. Je reçois des attaques OnTouchEvent imprévisibles avec le code d'action ACTION_MOVE. Je n'ai jamais (ou presque jamais) la séquence ACTION_DOWN -> ACTION_UP.
Je ne peux pas non plus utiliser OnClickListener car il ne donne pas la position du clic. Alors, comment puis-je détecter un événement de clic et le différencier d'un déplacement?
Voici une autre solution très simple qui ne vous oblige pas à vous inquiéter du déplacement du doigt. Si vous basez un clic sur la distance simplement déplacée, comment pouvez-vous différencier un clic et un clic long?.
Vous pouvez ajouter plus d'intelligence à cela et inclure la distance parcourue, mais je ne suis pas encore tombé sur une instance où la distance qu'un utilisateur peut parcourir en 200 millisecondes devrait constituer un déplacement par opposition à 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;
}
});
J'ai eu les meilleurs résultats en prenant en compte:
ACTION_DOWN
et ACTION_UP
. Je voulais spécifier la distance maximale autorisée en pixels indépendants de la densité plutôt qu'en pixels, afin de mieux prendre en charge différents écrans. Par exemple, 15 DP.Exemple:
/**
* 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;
}
L'idée ici est la même que dans Solution de Gem , avec ces différences:
Mise à jour (2015): consultez également La version ajustée de Gabriel .
Prendre l’avance de Jonik j’ai construit une version légèrement plus précise, qui n’est pas enregistrée comme un clic si vous déplacez votre doigt puis revenez à l’endroit avant de vous laisser aller:
Alors voici ma solution:
/**
* 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;
}
Utilisez le détecteur, cela fonctionne, et il ne se soulèvera pas en cas de glisser
Champ:
private GestureDetector mTapDetector;
Initialiser:
mTapDetector = new GestureDetector(context,new GestureTap());
Classe intérieure:
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;
}
}
onTouch:
@Override
public boolean onTouch(View v, MotionEvent event) {
mTapDetector.onTouchEvent(event);
return true;
}
Prendre plaisir :)
Pour obtenir la reconnaissance la plus optimisée de l'événement de clic Nous devons considérer 2 choses:
En fait, je combine la logique donnée par Stimsoni et Neethirajan
Alors voici ma solution:
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;
}
});
Le code ci-dessous résoudra votre problème
@Passer outre public boolean onTouchEvent (événement MotionEvent) { switch (event.getAction ()) { case (MotionEvent.ACTION_DOWN): x1 = event.getX (); y1 = event.getY (); Pause; case (MotionEvent.ACTION_UP): { x2 = event.getX (); y2 = event.getY (); dx = x2-x1; dy = y2-y1; if (Math.abs (dx)> Math.abs (dy)) { si (dx> 0) déplacez (1); //droite sinon si (dx == 0) move (5); //Cliquez sur sinon déplacer (2); //la gauche } autre { si (dy> 0) déplacez (3); // vers le bas sinon si (dy == 0) move (5); //Cliquez sur sinon bouger (4); //up____. } } } retourne vrai; }
Il est très difficile pour ACTION_DOWN de se produire sans ACTION_MOVE. Le moindre basculement de votre doigt sur l'écran à un endroit différent de celui où le premier contact a eu lieu déclenchera l'événement MOVE. De plus, je crois qu'un changement de pression au doigt déclenchera également l'événement MOVE. J'utiliserais une instruction if dans la méthode Action_Move pour tenter de déterminer la distance à laquelle le mouvement s'est produit par rapport au mouvement DOWN d'origine. si le mouvement s'est produit en dehors d'un rayon défini, votre action MOVE se produira. Ce n'est probablement pas la meilleure façon, efficace en ressources, de faire ce que vous essayez mais cela devrait fonctionner.
Si vous souhaitez réagir uniquement au clic, utilisez:
if (event.getAction() == MotionEvent.ACTION_UP) {
}
En ajoutant aux réponses ci-dessus, si vous voulez implémenter les actions onClick et Drag, alors mon code ci-dessous peut vous les gars. Prenant un peu de l'aide 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;
}
En utilisant Gil SH answer, je l’ai amélioré en implémentant onSingleTapUp()
plutôt que onSingleTapConfirmed()
. Il est beaucoup plus rapide et ne cliquera pas sur la vue s’il est déplacé.
GestureTap:
public class GestureTap extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
button.performClick();
return true;
}
}
Utilisez-le comme:
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;
}
});