68 votes

Détecter la pression tactile, la pression longue ou le mouvement ?

Je m'amuse actuellement à programmer Android, mais j'ai un petit problème pour détecter différents événements tactiles, à savoir une pression normale (appuyer sur l'écran et relâcher immédiatement), une pression longue (toucher l'écran et maintenir le doigt dessus) et un mouvement (glisser sur l'écran).

Ce que je voulais faire, c'est avoir une image (d'un cercle) sur mon écran que je peux déplacer. Ensuite, lorsque j'appuie une fois dessus (appui court/normal), un Toast s'affiche avec quelques informations de base sur l'image. Lorsque j'appuie longuement sur l'image, une fenêtre d'alerte s'affiche avec une liste permettant de sélectionner une autre image (cercle, rectangle ou triangle).

J'ai créé une vue personnalisée avec mon propre OnTouchListener pour détecter les événements et dessiner l'image dans onDraw. Le OnTouchListener.onTouch ressemble à ceci :

// has a touch press started?
private boolean touchStarted = false;
// co-ordinates of image
private int x, y;

public boolean onTouch(View v, MotionEvent event) {
    int action = event.getAction();
    if (action == MotionEvent.ACTION_DOWN) {
        touchStarted = true;
    }
    else if (action == MotionEvent.ACTION_MOVE) {
        // movement: cancel the touch press
        touchStarted = false;

        x = event.getX();
        y = event.getY();

        invalidate(); // request draw
    }
    else if (action == MotionEvent.ACTION_UP) {
        if (touchStarted) {
            // touch press complete, show toast
            Toast.makeText(v.getContext(), "Coords: " + x + ", " + y, 1000).show();
        }
    }

    return true;
}

Le problème, c'est que la pression ne fonctionne pas comme prévu, car lorsque je touche l'écran avec désinvolture, il détecte également un petit mouvement et annule la pression tactile pour déplacer l'image à la place.

J'ai "bidouillé" un peu en introduisant une nouvelle variable "mTouchDelay" que je fixe à 0 sur ACTION_DOWN, que j'augmente dans MOVE et si elle est >= 3 dans MOVE, j'exécute mon code de "déplacement". Mais j'ai l'impression que ce n'est pas vraiment la bonne méthode.

Je n'ai pas non plus trouvé comment détecter un appui long. Le coupable est vraiment le MOVE qui semble toujours se déclencher.

Pour un exemple de ce que je veux en gros, voyez l'application Android "DailyStrip" : elle montre l'image d'une bande dessinée. Vous pouvez la faire glisser si elle est trop grande pour l'écran. Vous pouvez l'effleurer une fois pour faire apparaître certaines commandes et l'appuyer longuement pour afficher un menu d'options.

PS. J'essaie de le faire fonctionner sur Android 1.5, puisque mon téléphone ne fonctionne que sur 1.5.

0 votes

Cela nécessite une balise Java. Cependant, si vous parvenez un jour à mettre en place cette fonctionnalité dans une application Web, accessible via jQuery, j'essaie de trouver comment intercepter également le tapotement long (tapotement maintenu, pression longue).

95voto

MSquare Points 1232

Ce code peut faire la distinction entre un clic et un mouvement (glisser, faire défiler). Dans onTouchEvent, définissez un drapeau isOnClick, et les coordonnées initiales X, Y sur ACTION_DOWN. Effacez l'indicateur sur ACTION_MOVE (en gardant à l'esprit qu'un mouvement non intentionnel est souvent détecté, ce qui peut être résolu avec une constante THRESHOLD).

private float mDownX;
private float mDownY;
private final float SCROLL_THRESHOLD = 10;
private boolean isOnClick;

@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            mDownX = ev.getX();
            mDownY = ev.getY();
            isOnClick = true;
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            if (isOnClick) {
                Log.i(LOG_TAG, "onClick ");
                //TODO onClick code
            }
            break;
        case MotionEvent.ACTION_MOVE:
            if (isOnClick && (Math.abs(mDownX - ev.getX()) > SCROLL_THRESHOLD || Math.abs(mDownY - ev.getY()) > SCROLL_THRESHOLD)) {
                Log.i(LOG_TAG, "movement detected");
                isOnClick = false;
            }
            break;
        default:
            break;
    }
    return true;
}

Pour LongPress, comme suggéré ci-dessus, GestureDetector est la solution. Consultez ce Q&A :

Détecter un appui long avec Android

1 votes

A tout moment ! :) Merci de votre reconnaissance ;)

2 votes

Que fait le & MotionEvent.ACTION_MASK faire ?

0 votes

MotionEvent.ACTION_MASK est un masque binaire appliqué avec l'opération ET par bit. Il filtre l'index du pointeur et affiche uniquement l'action du MotionEvent.

22voto

Jason L. Points 1546

Extrait de la documentation Android -

onLongClick()

De View.OnLongClickListener. Il est appelé lorsque l'utilisateur touche et maintient l'élément (en mode tactile), ou se concentre sur l'élément avec les touches de navigation ou la boule de commande et appuie et maintient la touche "entrée" appropriée ou appuie et maintient la boule de commande (pendant une seconde).

onTouch()

De View.OnTouchListener. Il est appelé lorsque l'utilisateur effectue une action qualifiée d'événement tactile, notamment une pression, un relâchement ou tout autre geste de déplacement sur l'écran (dans les limites de l'élément).

En ce qui concerne le "déplacement se produit même lorsque je touche", je définirais un delta et m'assurerais que la vue a été déplacée d'au moins ce delta avant de déclencher le code de déplacement. Si ce n'est pas le cas, je déclencherais le code de toucher.

0 votes

Avec onLongClick, le clic long fonctionne "plus ou moins", mais je suis toujours confronté à deux problèmes. Tout d'abord, onLongClick se déclenche deux fois pour un clic long (je montre simplement un Toast et renvoie true, et j'obtiens 2 Toasts). De plus, lorsque j'appuie et que je maintiens la pression, j'obtiens le clic long tout en maintenant la pression, mais lorsque je relâche, j'obtiens toujours l'ACTION_UP de l'onTouch. Comment puis-je empêcher l'ACTION_UP de l'onTouch après le déclenchement de onLongClick ?

0 votes

Le double onLongClick m'inquiète, je ne vois pas pourquoi il se déclencherait deux fois. Quant à l'ACTION_UP qui se déclenche toujours, je me demande pourquoi vous avez besoin de suivre l'ACTION_UP et l'ACTION_DOWN. Testez pour voir si onTouch() se déclenche lorsque onLongClick() se déclenche. Si ce n'est pas le cas, je ne suis pas sûr que vous ayez besoin de les capturer. Vous ne devriez avoir besoin de onTouch() que lorsque l'élément est réellement touché et lorsqu'il est déplacé. Vous pouvez également envisager d'utiliser onClick() pour "onTouch()" et d'utiliser onTouch() pour le mouvement uniquement. Mais je ne suis pas sûr que cela réponde à vos besoins.

15voto

Sanjay Manohar Points 3612

J'ai découvert cela après de nombreuses expérimentations.

Dans l'initialisation de votre activité :

setOnLongClickListener(new View.OnLongClickListener() {
  public boolean onLongClick(View view) {
    activity.openContextMenu(view);  
    return true;  // avoid extra click events
  }
});
setOnTouch(new View.OnTouchListener(){
  public boolean onTouch(View v, MotionEvent e){
    switch(e.getAction & MotionEvent.ACTION_MASK){
      // do drag/gesture processing. 
    }
    // you MUST return false for ACTION_DOWN and ACTION_UP, for long click to work
    // you can return true for ACTION_MOVEs that you consume. 
    // DOWN/UP are needed by the long click timer.
    // if you want, you can consume the UP if you have made a drag - so that after 
    // a long drag, no long-click is generated.
    return false;
  }
});
setLongClickable(true);

0 votes

Que voulez-vous dire par "consommer l'UP" ? Désolé, je suis débutant.

2 votes

Oh - Si un événement m'est envoyé, je peux le "consommer", ce qui signifie que les autres hooks qui écoutent le même événement ne sont pas appelés eux aussi. Par exemple, lorsque je lève ma main de l'écran, un message "UP" peut être envoyé le long de la hiérarchie des composants à moins que je ne le consomme.

0 votes

Je vois. Je comprends ce que vous avez écrit en principe, mais comment " consommer " concrètement, par exemple, l'événement " UP " ?

3voto

voytez Points 450

Si vous devez faire la distinction entre un clic, une pression longue et un défilement, utilisez GestureDetector.

L'activité met en œuvre GestureDetector.OnGestureListener

puis créer le détecteur dans onCreate par exemple

mDetector = new GestureDetectorCompat(getActivity().getApplicationContext(),this);

puis, si vous le souhaitez, placez un setOnTouchListener sur votre vue (par exemple webview) où

onTouch(View v, MotionEvent event) {
return mDetector.onTouchEvent(event);
}

et maintenant vous pouvez utiliser Override onScroll, onFling, showPress( détecter un appui long) ou onSingleTapUp (détecter un clic)

3voto

rajankz Points 151

Je recherchais une solution similaire et voici ce que je propose. Dans la méthode OnTouch, enregistrez le temps pour l'événement MotionEvent.ACTION_DOWN, puis pour MotionEvent.ACTION_UP, enregistrez à nouveau le temps. De cette façon, vous pouvez également définir votre propre seuil. Après avoir expérimenté plusieurs fois, vous connaîtrez le temps maximum en millisecondes nécessaire à l'enregistrement d'un simple toucher et vous pourrez l'utiliser dans la méthode move ou autre comme vous le souhaitez.

J'espère que cela vous a aidé. Veuillez commenter si vous avez utilisé une méthode différente et résolu votre problème.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X