web-dev-qa-db-fra.com

Comment faire la distinction entre déplacer et cliquer sur onTouchEvent ()?

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? 

64
Anastasia

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

J'ai eu les meilleurs résultats en prenant en compte:

  1. Principalement, le distance s'est déplacé entre les événements 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.
  2. Secondairement, la durée entre les événements. Une seconde semblait bien aller au maximum. (Certaines personnes "cliquent" assez "profondément", c’est-à-dire lentement; je veux toujours le reconnaître.)

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:

  • Ceci calcule la distance réelle euclidienne entre les deux points.
  • Ceci utilise dp au lieu de px.

Mise à jour (2015): consultez également La version ajustée de Gabriel .

48
Jonik

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

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

14
Gil SH

Pour obtenir la reconnaissance la plus optimisée de l'événement de clic Nous devons considérer 2 choses:

  1. Décalage horaire entre ACTION_DOWN et ACTION_UP. 
  2. Différence entre x, y lorsque l’utilisateur touche et relâche le doigt.

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

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

4
Neethirajan

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.

3
testingtester

Si vous souhaitez réagir uniquement au clic, utilisez:

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

}
1
YTerle

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;
    }
1
Rushi Ayyappa

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;
    }
});
0
Hussein El Feky