Détection de mouvements de jet sur la disposition de la grille


1106

Je souhaite que la flingdétection des gestes fonctionne dans mon application Android.

Ce que j'ai est un GridLayoutqui contient 9 ImageViews. La source se trouve ici: Romain Guys's Grid Layout .

Ce fichier que je prends est issu de l' application Photostream de Romain Guy et n'a été que légèrement adapté.

Pour la situation de clic simple, je n'ai besoin que de définir onClickListenerpour chaque ImageViewque j'ajoute être le principal activityqui implémente View.OnClickListener. Il semble infiniment plus compliqué de mettre en œuvre quelque chose qui reconnaît a fling. Je suppose que c'est parce que cela peut s'étendre views?

  • Si mon activité implémente, OnGestureListenerje ne sais pas comment définir cela comme écouteur de gestes pour la Gridou les Imagevues que j'ajoute.

    public class SelectFilterActivity extends Activity implements
       View.OnClickListener, OnGestureListener { ...
    
  • Si mon activité est implémentée, OnTouchListenerje n'ai aucune onFlingméthode pour le faire override(elle a deux événements comme paramètres me permettant de déterminer si le lancer était notable).

    public class SelectFilterActivity extends Activity implements
        View.OnClickListener, OnTouchListener { ...
    
  • Si j'effectue une personnalisation View, comme GestureImageViewcelle-ci, ImageViewje ne sais pas comment dire à l'activité qu'il flings'est produit depuis la vue. Dans tous les cas, j'ai essayé et les méthodes n'étaient pas appelées lorsque j'ai touché l'écran.

J'ai vraiment besoin d'un exemple concret de ce travail à travers les vues. Quoi, quand et comment dois-je le joindre listener? Je dois également pouvoir détecter les clics simples.

// Gesture detection
mGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {

    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        int dx = (int) (e2.getX() - e1.getX());
        // don't accept the fling if it's too short
        // as it may conflict with a button push
        if (Math.abs(dx) > MAJOR_MOVE && Math.abs(velocityX) > Math.absvelocityY)) {
            if (velocityX > 0) {
                moveRight();
            } else {
                moveLeft();
            }
            return true;
        } else {
            return false;
        }
    }
});

Est-il possible de placer une vue transparente sur le haut de mon écran pour capturer des flings?

Si je choisis de ne pas voir inflatemes images enfant à partir de XML, puis-je passer le GestureDetectorparamètre en tant que constructeur à une nouvelle sous-classe ImageViewque je crée?

C'est l'activité très simple pour laquelle j'essaie de faire fonctionner la flingdétection: SelectFilterActivity (Adapté de photostream) .

J'ai regardé ces sources:

Jusqu'à présent, rien n'a fonctionné pour moi et j'espérais quelques indications.


Comment résoudre ce problème? Veuillez répondre à stackoverflow.com/questions/60464912/…
Bishwash

Réponses:


818

Merci à Code Shogun , dont j'ai adapté le code à ma situation.

Laissez votre activité se dérouler OnClickListenercomme d'habitude:

public class SelectFilterActivity extends Activity implements OnClickListener {

  private static final int SWIPE_MIN_DISTANCE = 120;
  private static final int SWIPE_MAX_OFF_PATH = 250;
  private static final int SWIPE_THRESHOLD_VELOCITY = 200;
  private GestureDetector gestureDetector;
  View.OnTouchListener gestureListener;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    /* ... */

    // Gesture detection
    gestureDetector = new GestureDetector(this, new MyGestureDetector());
    gestureListener = new View.OnTouchListener() {
      public boolean onTouch(View v, MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
      }
    };

  }

  class MyGestureDetector extends SimpleOnGestureListener {
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
      try {
        if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
          return false;
        // right to left swipe
        if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
          Toast.makeText(SelectFilterActivity.this, "Left Swipe", Toast.LENGTH_SHORT).show();
        } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
          Toast.makeText(SelectFilterActivity.this, "Right Swipe", Toast.LENGTH_SHORT).show();
        }
      } catch (Exception e) {
         // nothing
      }
      return false;
    }

    @Override
    public boolean onDown(MotionEvent e) {
      return true;
    }
  }
}

Attachez votre écouteur de gestes à toutes les vues que vous ajoutez à la disposition principale;

// Do this for each view added to the grid
imageView.setOnClickListener(SelectFilterActivity.this); 
imageView.setOnTouchListener(gestureListener);

Observez avec émerveillement vos méthodes remplacées, à la fois onClick(View v)de l'activité et onFlingde l'auditeur de gestes.

public void onClick(View v) {
  Filter f = (Filter) v.getTag();
  FilterFullscreenActivity.show(this, input, f);
}

La danse «fling» est facultative mais encouragée.


109
Merci pour ce code! Ce fut très utile. Cependant, je suis tombé sur une prise très frustrante en essayant de faire fonctionner les gestes. Dans mon SimpleOnGestureListener, je dois remplacer onDown pour l'un de mes gestes à enregistrer. Il peut simplement renvoyer true mais i doit être défini. PS: Je ne sais pas si c'est ma révision de l'API ou mon matériel, mais j'utilise 1.5 sur un HTC Droid Eris.
Cdsboy

J'ai essayé votre code et peu importe si je glisse ou clique (avec ma souris, car je travaille dans l'émulateur), j'obtiens toujours un Toast que j'ai défini dans la méthode onClick, de sorte que l'émulateur ne détecte que les clics, sans glisser. Pourquoi en est-il ainsi?
lomza

J'ai essayé ce code et cela n'a pas fonctionné. n'a toujours pas pu défiler du tout lorsque j'applique un écouteur onClick à l'une des vues enfant dans ma vue galerie
Jonathan

Iomza: avez-vous essayé de mettre des instructions break et de parcourir votre code?
IgorGanapolsky

Bravo pour l'utilisation d'une classe intérieure! Approche très propre.
IgorGanapolsky

211

L'une des réponses ci-dessus mentionne la gestion de différentes densités de pixels mais suggère de calculer les paramètres de balayage à la main. Il convient de noter que vous pouvez réellement obtenir des valeurs échelonnées et raisonnables du système à l'aide de la ViewConfigurationclasse:

final ViewConfiguration vc = ViewConfiguration.get(getContext());
final int swipeMinDistance = vc.getScaledPagingTouchSlop();
final int swipeThresholdVelocity = vc.getScaledMinimumFlingVelocity();
final int swipeMaxOffPath = vc.getScaledTouchSlop();
// (there is also vc.getScaledMaximumFlingVelocity() one could check against)

J'ai remarqué que l'utilisation de ces valeurs rend la "sensation" de fling plus cohérente entre l'application et le reste du système.


11
J'utilise swipeMinDistance = vc.getScaledPagingTouchSlop()et swipeMaxOffPath = vc.getScaledTouchSlop().
Thomas Ahle

8
getScaledTouchSlopme donne très peu de résultat de décalage, maladroitement. Par exemple, seulement 24 pixels sur un écran d'une hauteur de 540, c'est très difficile de le garder à portée avec le doigt. : S
WonderCsabo

148

Je le fais un peu différemment, et j'ai écrit une classe de détecteurs supplémentaire qui implémente le View.onTouchListener

onCreateest simplement l'ajouter à la mise en page la plus basse comme ceci:

ActivitySwipeDetector activitySwipeDetector = new ActivitySwipeDetector(this);
lowestLayout = (RelativeLayout)this.findViewById(R.id.lowestLayout);
lowestLayout.setOnTouchListener(activitySwipeDetector);

où id.lowestLayout est l'id.xxx pour la vue la plus basse dans la hiérarchie de mise en page et le plus basLayout est déclaré en tant que RelativeLayout

Et puis il y a la classe réelle du détecteur de balayage d'activité:

public class ActivitySwipeDetector implements View.OnTouchListener {

static final String logTag = "ActivitySwipeDetector";
private Activity activity;
static final int MIN_DISTANCE = 100;
private float downX, downY, upX, upY;

public ActivitySwipeDetector(Activity activity){
    this.activity = activity;
}

public void onRightSwipe(){
    Log.i(logTag, "RightToLeftSwipe!");
    activity.doSomething();
}

public void onLeftSwipe(){
    Log.i(logTag, "LeftToRightSwipe!");
    activity.doSomething();
}

public void onDownSwipe(){
    Log.i(logTag, "onTopToBottomSwipe!");
    activity.doSomething();
}

public void onUpSwipe(){
    Log.i(logTag, "onBottomToTopSwipe!");
    activity.doSomething();
}

public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

       // swipe horizontal?
        if(Math.abs(deltaX) > Math.abs(deltaY))
        {
            if(Math.abs(deltaX) > MIN_DISTANCE){
                // left or right
                if(deltaX > 0) { this.onRightSwipe(); return true; }
                if(deltaX < 0) { this.onLeftSwipe(); return true; }
            }
            else {
                    Log.i(logTag, "Horizontal Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                    return false; // We don't consume the event
            }
        }
        // swipe vertical?
        else 
        {
            if(Math.abs(deltaY) > MIN_DISTANCE){
                // top or down
                if(deltaY < 0) { this.onDownSwipe(); return true; }
                if(deltaY > 0) { this.onUpSwipe(); return true; }
            }
            else {
                    Log.i(logTag, "Vertical Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                    return false; // We don't consume the event
            }
        }

            return true;
        }
    }
    return false;
}

}

Fonctionne vraiment bien pour moi!


1
En fait, cela m'a beaucoup facilité l'application de la fonctionnalité gestuelle et a nécessité "moins" de câblage: D Merci @Thomas
nemesisfixx

5
Cela ressemble à une classe utilitaire soignée - mais je pense que vos quatre méthodes sur ... swipe () devraient être des interfaces
Someone Somewhere

2
ces retours ne devraient pas être là (ligne "on ne consomme pas l'événement"), n'est-ce pas? Il désactive la fonction de défilement vertical.
Marek Sebera

5
en particulier, la méthode onTouch (). tout d'abord, si le delta X n'est pas assez grand, il revient sans vérifier le delta Y. le résultat est qu'il ne détecte jamais les balayages gauche-droite. deuxièmement, il ne devrait pas non plus retourner vrai s'il tombe sans trouver de balayage. troisièmement, il ne devrait pas retourner vrai lors d'une action vers le bas. cela empêche tout autre écouteur comme onClick de fonctionner.
Jeffrey Blattman

1
@Piotr ce n'est pas un problème tant que l'objet contenant la référence a la même portée que l'activité elle-même. le problème se produit lorsque vous conservez une référence à une activité dans un endroit qui a une portée plus grande que l'activité ... comme par exemple d'un membre statique.
Jeffrey Blattman

94

J'ai légèrement modifié et réparé la solution de Thomas Fankhauser

L'ensemble du système se compose de deux fichiers, SwipeInterface et ActivitySwipeDetector


SwipeInterface.java

import android.view.View;

public interface SwipeInterface {

    public void bottom2top(View v);

    public void left2right(View v);

    public void right2left(View v);

    public void top2bottom(View v);

}

Détecteur

import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class ActivitySwipeDetector implements View.OnTouchListener {

    static final String logTag = "ActivitySwipeDetector";
    private SwipeInterface activity;
    static final int MIN_DISTANCE = 100;
    private float downX, downY, upX, upY;

    public ActivitySwipeDetector(SwipeInterface activity){
        this.activity = activity;
    }

    public void onRightToLeftSwipe(View v){
        Log.i(logTag, "RightToLeftSwipe!");
        activity.right2left(v);
    }

    public void onLeftToRightSwipe(View v){
        Log.i(logTag, "LeftToRightSwipe!");
        activity.left2right(v);
    }

    public void onTopToBottomSwipe(View v){
        Log.i(logTag, "onTopToBottomSwipe!");
        activity.top2bottom(v);
    }

    public void onBottomToTopSwipe(View v){
        Log.i(logTag, "onBottomToTopSwipe!");
        activity.bottom2top(v);
    }

    public boolean onTouch(View v, MotionEvent event) {
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

            // swipe horizontal?
            if(Math.abs(deltaX) > MIN_DISTANCE){
                // left or right
                if(deltaX < 0) { this.onLeftToRightSwipe(v); return true; }
                if(deltaX > 0) { this.onRightToLeftSwipe(v); return true; }
            }
            else {
                Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
            }

            // swipe vertical?
            if(Math.abs(deltaY) > MIN_DISTANCE){
                // top or down
                if(deltaY < 0) { this.onTopToBottomSwipe(v); return true; }
                if(deltaY > 0) { this.onBottomToTopSwipe(v); return true; }
            }
            else {
                Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                v.performClick();
            }
        }
        }
        return false;
    }

}

il est utilisé comme ceci:

ActivitySwipeDetector swipe = new ActivitySwipeDetector(this);
LinearLayout swipe_layout = (LinearLayout) findViewById(R.id.swipe_layout);
swipe_layout.setOnTouchListener(swipe);

Et dans l'implémentation, Activityvous devez implémenter des méthodes à partir de SwipeInterface , et vous pouvez savoir sur quel affichage l'événement Swipe a été appelé.

@Override
public void left2right(View v) {
    switch(v.getId()){
        case R.id.swipe_layout:
            // do your stuff here
        break;
    }       
}

Je l'ai légèrement modifié à nouveau, voir le v.performClick();, qui est utilisé pour ne pas consommer l'événement sur OnClickListener, s'il est défini sur la même vue
Marek Sebera

Salut, je suis un débutant total, donc cette question peut être vraiment évidente ou triviale, mais veuillez répondre. La partie où vous avez écrit, elle est utilisée comme: ActivitySwipeDetector swipe = new ActivitySwipeDetector (this); Cette déclaration fera partie de MainActivity, n'est-ce pas? Ensuite, "ceci" sera une activité de MainActivity. Alors que le constructeur prend une instance de SwipeInterface. Veuillez m'aider ici. Merci beaucoup.
Chocolava

@Chocolava créer une nouvelle question, le commentaire n'est pas un bon endroit pour poser comme ça.
Marek Sebera

@MarekSebera cela ne fonctionne pas avec ScrollView & ListView? comment les gérer?
Duc Tran

@silentbang encore, ce n'est pas l'endroit pour poser de telles questions. veuillez créer un nouveau fil de questions.
Marek Sebera

65

Le code du détecteur de geste de balayage ci-dessus est très utile! Vous pouvez cependant souhaiter rendre cette densité de solution agnostique en utilisant les valeurs relatives suivantes (REL_SWIPE)plutôt que les valeurs absolues(SWIPE_)

DisplayMetrics dm = getResources().getDisplayMetrics();

int REL_SWIPE_MIN_DISTANCE = (int)(SWIPE_MIN_DISTANCE * dm.densityDpi / 160.0f);
int REL_SWIPE_MAX_OFF_PATH = (int)(SWIPE_MAX_OFF_PATH * dm.densityDpi / 160.0f);
int REL_SWIPE_THRESHOLD_VELOCITY = (int)(SWIPE_THRESHOLD_VELOCITY * dm.densityDpi / 160.0f);

8
+1 pour avoir soulevé cette question. Notez que DensityMetrics.densityDpi a été introduit dans l'API 4. Pour une compatibilité descendante avec l'API 1, utilisez plutôt DensityMetrics.density. Cela modifie ensuite le calcul pour qu'il soit juste SWIPE_MIN_DISTANCE * dm.density.
Thane Anthem

Où avez-vous obtenu le numéro 160.0f?
IgorGanapolsky

developer.android.com/guide/practices/screens_support.html Pixel indépendant de la densité (dp) La conversion des unités dp en pixels de l'écran est simple: px = dp * (dpi / 160)
paiego

Je cherchais partout pour ça. AUCUN exemple de onFling () sur Internet n'a cela, ce qui conduira à une mauvaise UX. Merci!
Sandy

160.0f est le vient du 160 DPI qui est la densité standard sur laquelle le DP (pixels indépendants de la densité) est basé. public static final int DENSITY_MEDIUM Ajouté au niveau 4 de l'API DPI quantifié standard pour les écrans de densité moyenne. Valeur constante: 160 (0x000000a0)
paiego

35

Ma version de solution proposée par Thomas Fankhauser et Marek Sebera (ne gère pas les balayages verticaux):

SwipeInterface.java

import android.view.View;

public interface SwipeInterface {

    public void onLeftToRight(View v);

    public void onRightToLeft(View v);
}

ActivitySwipeDetector.java

import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

public class ActivitySwipeDetector implements View.OnTouchListener {

    static final String logTag = "ActivitySwipeDetector";
    private SwipeInterface activity;
    private float downX, downY;
    private long timeDown;
    private final float MIN_DISTANCE;
    private final int VELOCITY;
    private final float MAX_OFF_PATH;

    public ActivitySwipeDetector(Context context, SwipeInterface activity){
        this.activity = activity;
        final ViewConfiguration vc = ViewConfiguration.get(context);
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        MIN_DISTANCE = vc.getScaledPagingTouchSlop() * dm.density;
        VELOCITY = vc.getScaledMinimumFlingVelocity();
        MAX_OFF_PATH = MIN_DISTANCE * 2;            
    }

    public void onRightToLeftSwipe(View v){
        Log.i(logTag, "RightToLeftSwipe!");
        activity.onRightToLeft(v);
    }

    public void onLeftToRightSwipe(View v){
        Log.i(logTag, "LeftToRightSwipe!");
        activity.onLeftToRight(v);
    }

    public boolean onTouch(View v, MotionEvent event) {
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            Log.d("onTouch", "ACTION_DOWN");
            timeDown = System.currentTimeMillis();
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            Log.d("onTouch", "ACTION_UP");
            long timeUp = System.currentTimeMillis();
            float upX = event.getX();
            float upY = event.getY();

            float deltaX = downX - upX;
            float absDeltaX = Math.abs(deltaX); 
            float deltaY = downY - upY;
            float absDeltaY = Math.abs(deltaY);

            long time = timeUp - timeDown;

            if (absDeltaY > MAX_OFF_PATH) {
                Log.i(logTag, String.format("absDeltaY=%.2f, MAX_OFF_PATH=%.2f", absDeltaY, MAX_OFF_PATH));
                return v.performClick();
            }

            final long M_SEC = 1000;
            if (absDeltaX > MIN_DISTANCE && absDeltaX > time * VELOCITY / M_SEC) {
                if(deltaX < 0) { this.onLeftToRightSwipe(v); return true; }
                if(deltaX > 0) { this.onRightToLeftSwipe(v); return true; }
            } else {
                Log.i(logTag, String.format("absDeltaX=%.2f, MIN_DISTANCE=%.2f, absDeltaX > MIN_DISTANCE=%b", absDeltaX, MIN_DISTANCE, (absDeltaX > MIN_DISTANCE)));
                Log.i(logTag, String.format("absDeltaX=%.2f, time=%d, VELOCITY=%d, time*VELOCITY/M_SEC=%d, absDeltaX > time * VELOCITY / M_SEC=%b", absDeltaX, time, VELOCITY, time * VELOCITY / M_SEC, (absDeltaX > time * VELOCITY / M_SEC)));
            }

        }
        }
        return false;
    }

}

quelqu'un peut-il me dire comment appeler la classe. ActivitySwipeDetector swipe = new ActivitySwipeDetector (this); donne évidemment une erreur, car aucun constructeur de ce type. Dois-je donner ActivitySwipeDetector swipe = new ActivitySwipeDetector (this, null);
abdfahim

@AbdullahFahim ActivitySwipeDetector (this, YourActivity.this);
Anton Kashpor

25

Cette question est un peu ancienne et en juillet 2011, Google a publié le paquet de compatibilité, révision 3) qui inclut celui ViewPagerqui fonctionne avec Android 1.6 vers le haut. Les GestureListenerréponses publiées pour cette question ne sont pas très élégantes sur Android. Si vous recherchez le code utilisé pour basculer entre les photos dans la galerie Android ou pour changer de vue dans la nouvelle application Play Market, c'est certainementViewPager .

Voici quelques liens pour plus d'informations:


Un problème avec ViewPager est que vous n'avez aucun contrôle sur les paramètres de distance et de vitesse pour le geste de lancer.
almalkawi

ViewPager n'est pas utilisé dans la galerie.
Anthony

18

Il y a une interface intégrée que vous pouvez utiliser directement pour tous les gestes:
Voici une explication pour un utilisateur de niveau basique: entrez la description de l'image ici Il y a 2 importations soyez prudent en choisissant que les deux sont différents entrez la description de l'image ici entrez la description de l'image ici


1
Et quelles sont les prochaines étapes? Comment définir cet écouteur sur une vue particulière? Et si cette vue fait partie d'un fragment?
Stan

16

Il y a une proposition sur le Web (et cette page) pour utiliser ViewConfiguration. getScaledTouchSlop () pour avoir une valeur à l'échelle du périphérique pour SWIPE_MIN_DISTANCE.

getScaledTouchSlop()est destiné au " défilement " distance "seuil de ", pas à glisser. La distance de seuil de défilement doit être inférieure à une distance de seuil de "basculement entre les pages". Par exemple, cette fonction renvoie 12 pixels sur mon Samsung GS2, et les exemples cités dans cette page font environ 100 pixels.

Avec l'API niveau 8 (Android 2.2, Froyo), vous avez getScaledPagingTouchSlop(), destiné au balayage de page. Sur mon appareil, il renvoie 24 (pixels). Donc, si vous êtes au niveau API <8, je pense que "2 * getScaledTouchSlop()" devrait être le seuil de balayage "standard". Mais les utilisateurs de mon application avec de petits écrans m'ont dit que c'était trop peu ... Comme sur mon application, vous pouvez faire défiler verticalement, et changer de page horizontalement. Avec la valeur proposée, ils changent parfois de page au lieu de défiler.


13

Aussi comme amélioration mineure.

La principale raison du bloc try / catch est que e1 pourrait être nul pour le mouvement initial. en plus du try / catch, incluez un test pour null et return. semblable au suivant

if (e1 == null || e2 == null) return false;
try {
...
} catch (Exception e) {}
return false;

12

Il y a beaucoup d'excellentes informations ici. Malheureusement, une grande partie de ce code de traitement de fling est dispersée sur divers sites dans différents états d'achèvement, même si l'on pourrait penser que cela est essentiel pour de nombreuses applications.

J'ai pris le temps de créer un auditeur fling qui vérifie que les conditions appropriées sont remplies. J'ai ajouté un écouteur de lancement de page qui ajoute plus de vérifications pour s'assurer que les lancements atteignent le seuil des lancements de pages. Ces deux écouteurs vous permettent de limiter facilement les flings à l'axe horizontal ou vertical. Vous pouvez voir comment il est utilisé dans une vue pour faire glisser des images . Je reconnais que les gens ici ont fait la plupart des recherches --- Je viens de les rassembler dans une bibliothèque utilisable.

Ces derniers jours représentent ma première tentative de codage sur Android; attendez beaucoup plus à venir.


Je veux implémenter le geste de balayage via 2 doigts. Sil te plait aide moi!
Gaurav Arora

12

Il s'agit d'une réponse combinée des deux réponses en haut, si quelqu'un veut une implémentation fonctionnelle.

package com.yourapplication;

import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

public abstract class OnSwipeListener implements View.OnTouchListener {

    private final GestureDetector gestureDetector;

    public OnSwipeListener(Context context){
        gestureDetector = new GestureDetector(context, new OnSwipeGestureListener(context));
        gestureDetector.setIsLongpressEnabled(false);
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
    }

    private final class OnSwipeGestureListener extends GestureDetector.SimpleOnGestureListener {

        private final int minSwipeDelta;
        private final int minSwipeVelocity;
        private final int maxSwipeVelocity;

        private OnSwipeGestureListener(Context context) {
            ViewConfiguration configuration = ViewConfiguration.get(context);
            // We think a swipe scrolls a full page.
            //minSwipeDelta = configuration.getScaledTouchSlop();
            minSwipeDelta = configuration.getScaledPagingTouchSlop();
            minSwipeVelocity = configuration.getScaledMinimumFlingVelocity();
            maxSwipeVelocity = configuration.getScaledMaximumFlingVelocity();
        }

        @Override
        public boolean onDown(MotionEvent event) {
            // Return true because we want system to report subsequent events to us.
            return true;
        }

        // NOTE: see http://stackoverflow.com/questions/937313/android-basic-gesture-detection
        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX,
                               float velocityY) {

            boolean result = false;
            try {
                float deltaX = event2.getX() - event1.getX();
                float deltaY = event2.getY() - event1.getY();
                float absVelocityX = Math.abs(velocityX);
                float absVelocityY = Math.abs(velocityY);
                float absDeltaX = Math.abs(deltaX);
                float absDeltaY = Math.abs(deltaY);
                if (absDeltaX > absDeltaY) {
                    if (absDeltaX > minSwipeDelta && absVelocityX > minSwipeVelocity
                            && absVelocityX < maxSwipeVelocity) {
                        if (deltaX < 0) {
                            onSwipeLeft();
                        } else {
                            onSwipeRight();
                        }
                    }
                    result = true;
                } else if (absDeltaY > minSwipeDelta && absVelocityY > minSwipeVelocity
                        && absVelocityY < maxSwipeVelocity) {
                    if (deltaY < 0) {
                        onSwipeTop();
                    } else {
                        onSwipeBottom();
                    }
                }
                result = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
    }

    public void onSwipeLeft() {}

    public void onSwipeRight() {}

    public void onSwipeTop() {}

    public void onSwipeBottom() {}
}

Merci pour une très bonne implémentation. En outre , je vous suggère de vérifier absDeltaY > minSwipeDelta, absVelocityY > minSwipeVelocity, absVelocityY < maxSwipeVelocityseulement dans le cas si minSwipeDelta ! = getScaledTouchSlop , minSwipeVelocity ! = getScaledMinimumFlingVelocity , maxSwipeVelocity ! = getScaledMaximumFlingVelocity , Par exemple pour vérifier que si ces soi-disant valeurs « par défaut » (je veux dire getScaledTouchSlop, getScaledMinimumFlingVelocity, getScaledMaximumFlingVelocity) sont mises à l' échelle ou modifiés en fonction de votre propre souhait.
Elia12345

Le fait est que selon le code source , les valeurs "par défaut" mentionnées sont déjà vérifiées par GestureDetector, et OnFling n'est déclenché que si elles sont confirmées (par la façon dont le déclenchement n'a lieu qu'en cas de ACTION_UP, pas ACTION_MOVEou ACTION_POINTER_UP, c'est-à-dire uniquement comme résultat du geste pleinement réalisé). (Je n'ai pas vérifié les autres versions d'API, donc les commentaires sont appréciés).
Elia12345

11

Vous pouvez utiliser la bibliothèque droidQuery pour gérer les flings, les clics, les longs clics et les événements personnalisés. L'implémentation est construite sur ma réponse précédente ci-dessous, mais droidQuery fournit une syntaxe simple et élégante :

//global variables    private boolean isSwiping = false;
private SwipeDetector.Direction swipeDirection = null;
private View v;//must be instantiated before next call.

//swipe-handling code
$.with(v).swipe(new Function() {
    @Override
    public void invoke($ droidQuery, Object... params) {
        if (params[0] == SwipeDetector.Direction.START)
            isSwiping = true;
        else if (params[0] == SwipeDetector.Direction.STOP) {
            if (isSwiping) {                    isSwiping = false;
                if (swipeDirection != null) {
                    switch(swipeDirection) {
                        case DOWN :                                //TODO: Down swipe complete, so do something
                            break;
                        case UP :
                            //TODO: Up swipe complete, so do something
                            break;
                        case LEFT :
                            //TODO: Left swipe complete, so do something
                            break;
                        case RIGHT :
                            //TODO: Right swipe complete, so do something
                            break;
                        default :                                break;
                    }
                }                }
        }
        else {
            swipeDirection = (SwipeDetector.Direction) params[0];
        }
    }
});

Réponse originale

Cette réponse utilise une combinaison de composants des autres réponses ici. Il se compose de la SwipeDetectorclasse, qui possède une interface interne pour écouter les événements. Je fournis également RelativeLayoutpour montrer comment passer outre un View« sonTouch méthode pour permettre aux deux événements de glissement et d' autres événements détectés (comme les clics ou longs clics).

SwipeDetector

package self.philbrown;

import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

/**
 * Detect Swipes on a per-view basis. Based on original code by Thomas Fankhauser on StackOverflow.com,
 * with adaptations by other authors (see link).
 * @author Phil Brown
 * @see <a href="http://stackoverflow.com/questions/937313/android-basic-gesture-detection">android-basic-gesture-detection</a>
 */
public class SwipeDetector implements View.OnTouchListener
{
    /**
     * The minimum distance a finger must travel in order to register a swipe event.
     */
    private int minSwipeDistance;

    /** Maintains a reference to the first detected down touch event. */
    private float downX, downY;

    /** Maintains a reference to the first detected up touch event. */
    private float upX, upY;

    /** provides access to size and dimension contants */
    private ViewConfiguration config;

    /**
     * provides callbacks to a listener class for various swipe gestures.
     */
    private SwipeListener listener;

    public SwipeDetector(SwipeListener listener)
    {
        this.listener = listener;
    }


    /**
     * {@inheritDoc}
     */
    public boolean onTouch(View v, MotionEvent event)
    {
        if (config == null)
        {
                config = ViewConfiguration.get(v.getContext());
                minSwipeDistance = config.getScaledTouchSlop();
        }

        switch(event.getAction())
        {
        case MotionEvent.ACTION_DOWN:
            downX = event.getX();
            downY = event.getY();
            return true;
        case MotionEvent.ACTION_UP:
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

            // swipe horizontal?
            if(Math.abs(deltaX) > minSwipeDistance)
            {
                // left or right
                if (deltaX < 0)
                {
                        if (listener != null)
                        {
                                listener.onRightSwipe(v);
                                return true;
                        }
                }
                if (deltaX > 0)
                {
                        if (listener != null)
                        {
                                listener.onLeftSwipe(v);
                                return true;
                        }
                }
            }

            // swipe vertical?
            if(Math.abs(deltaY) > minSwipeDistance)
            {
                // top or down
                if (deltaY < 0)
                {
                        if (listener != null)
                        {
                                listener.onDownSwipe(v);
                                return true;
                        }
                }
                if (deltaY > 0)
                {
                        if (listener != null)
                        {
                                listener.onUpSwipe(v);
                                return true;
                        }
                }
            }
        }
        return false;
    }

    /**
     * Provides callbacks to a registered listener for swipe events in {@link SwipeDetector}
     * @author Phil Brown
     */
    public interface SwipeListener
    {
        /** Callback for registering a new swipe motion from the bottom of the view toward its top. */
        public void onUpSwipe(View v);
        /** Callback for registering a new swipe motion from the left of the view toward its right. */
        public void onRightSwipe(View v);
        /** Callback for registering a new swipe motion from the right of the view toward its left. */
        public void onLeftSwipe(View v);
        /** Callback for registering a new swipe motion from the top of the view toward its bottom. */
        public void onDownSwipe(View v);
    }
}

Swipe Interceptor View

package self.philbrown;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.RelativeLayout;

import com.npeinc.module_NPECore.model.SwipeDetector;
import com.npeinc.module_NPECore.model.SwipeDetector.SwipeListener;

/**
 * View subclass used for handling all touches (swipes and others)
 * @author Phil Brown
 */
public class SwipeInterceptorView extends RelativeLayout
{
    private SwipeDetector swiper = null;

    public void setSwipeListener(SwipeListener listener)
    {
        if (swiper == null)
            swiper = new SwipeDetector(listener);
    }

    public SwipeInterceptorView(Context context) {
        super(context);
    }

    public SwipeInterceptorView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SwipeInterceptorView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onTouchEvent(MotionEvent e)
    {
        boolean swipe = false, touch = false;
        if (swiper != null)
            swipe = swiper.onTouch(this, e);
        touch = super.onTouchEvent(e);
        return swipe || touch;
    }
}

1
J'ai essayé de l'implémenter sur une vue qui contient des éléments cliquables. Lorsqu'un balayage recommence sur un élément cliquable (par exemple, une vue de liste sur laquelle l'écouteur onItemClick est enregistré), onTouchEvent n'est jamais invoqué. Ainsi, l'utilisateur ne peut pas lancer un balayage sur un élément cliquable, ce qui est regrettable pour moi et j'essaie toujours de trouver un moyen de contourner ce problème, car nos éléments cliquables occupent un peu d'espace de vue et nous voulons toujours le support de balayage pour la vue entière. Si un balayage ne démarre pas sur un élément cliquable, cela fonctionne parfaitement.
Lo-Tan

@ Lo-Tan, cela se produit car votre élément cliquable est une vue enfant, et se trouve donc au-dessus de SwipeInterceptorView, donc son clic est géré en premier. Vous pouvez résoudre ce problème en implémentant votre propre mécanisme de clic en implémentant onTouchListener, ou en guise de solution de rechange , vous pouvez écouter les clics longs au lieu de clics (voir View.setOnLongClickListener).
Phil

Je suis en train d'essayer cela en ce moment même. Ou annulation possible de l'événement de clic s'ils commencent un glissement :) Merci beaucoup.
Lo-Tan

Une solution consiste à attacher le détecteur de balayage à chaque vue de votre application. Une autre consiste à implémenter onInterceptTouchEvent dans votre SwipeInterceptorView.
Edward Falk

7

Je sais qu'il est trop tard pour répondre, mais je publie toujours Swipe Detection pour ListView qui explique comment utiliser Swipe Touch Listener dans ListView Item .

Réfrence: Exterminator13 (une des réponses sur cette page)

Créez un ActivitySwipeDetector.class

package com.example.wocketapp;

import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

public class ActivitySwipeDetector implements View.OnTouchListener 
{
    static final String logTag = "SwipeDetector";
    private SwipeInterface activity;
    private float downX, downY;
    private long timeDown;
    private final float MIN_DISTANCE;
    private final int VELOCITY;
    private final float MAX_OFF_PATH;

    public ActivitySwipeDetector(Context context, SwipeInterface activity)
    {
        this.activity = activity;
        final ViewConfiguration vc = ViewConfiguration.get(context);
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        MIN_DISTANCE = vc.getScaledPagingTouchSlop() * dm.density;
        VELOCITY = vc.getScaledMinimumFlingVelocity();
        MAX_OFF_PATH = MIN_DISTANCE * 2;
    }

    public void onRightToLeftSwipe(View v) 
    {
        Log.i(logTag, "RightToLeftSwipe!");
        activity.onRightToLeft(v);
    }

    public void onLeftToRightSwipe(View v) 
    {
        Log.i(logTag, "LeftToRightSwipe!");
        activity.onLeftToRight(v);
    }

    public boolean onTouch(View v, MotionEvent event) 
    {
        switch (event.getAction()) 
        {
            case MotionEvent.ACTION_DOWN:
            {
                Log.d("onTouch", "ACTION_DOWN");
                timeDown = System.currentTimeMillis();
                downX = event.getX();
                downY = event.getY();
                v.getParent().requestDisallowInterceptTouchEvent(false);
                return true;
            }

        case MotionEvent.ACTION_MOVE:
            {
                float y_up = event.getY();
                float deltaY = y_up - downY;
                float absDeltaYMove = Math.abs(deltaY);

                if (absDeltaYMove > 60) 
                {
                    v.getParent().requestDisallowInterceptTouchEvent(false);
                } 
                else
                {
                    v.getParent().requestDisallowInterceptTouchEvent(true);
                }
            }

            break;

            case MotionEvent.ACTION_UP: 
            {
                Log.d("onTouch", "ACTION_UP");
                long timeUp = System.currentTimeMillis();
                float upX = event.getX();
                float upY = event.getY();

                float deltaX = downX - upX;
                float absDeltaX = Math.abs(deltaX);
                float deltaY = downY - upY;
                float absDeltaY = Math.abs(deltaY);

                long time = timeUp - timeDown;

                if (absDeltaY > MAX_OFF_PATH) 
                {
                    Log.e(logTag, String.format(
                            "absDeltaY=%.2f, MAX_OFF_PATH=%.2f", absDeltaY,
                            MAX_OFF_PATH));
                    return v.performClick();
                }

                final long M_SEC = 1000;
                if (absDeltaX > MIN_DISTANCE && absDeltaX > time * VELOCITY / M_SEC) 
                {
                     v.getParent().requestDisallowInterceptTouchEvent(true);
                    if (deltaX < 0) 
                    {
                        this.onLeftToRightSwipe(v);
                        return true;
                    }
                    if (deltaX > 0) 
                    {
                        this.onRightToLeftSwipe(v);
                        return true;
                    }
                }
                else 
                {
                    Log.i(logTag,
                            String.format(
                                    "absDeltaX=%.2f, MIN_DISTANCE=%.2f, absDeltaX > MIN_DISTANCE=%b",
                                    absDeltaX, MIN_DISTANCE,
                                    (absDeltaX > MIN_DISTANCE)));
                    Log.i(logTag,
                            String.format(
                                    "absDeltaX=%.2f, time=%d, VELOCITY=%d, time*VELOCITY/M_SEC=%d, absDeltaX > time * VELOCITY / M_SEC=%b",
                                    absDeltaX, time, VELOCITY, time * VELOCITY
                                            / M_SEC, (absDeltaX > time * VELOCITY
                                            / M_SEC)));
                }

                 v.getParent().requestDisallowInterceptTouchEvent(false);

            }
        }
        return false;
    }
    public interface SwipeInterface 
    {

        public void onLeftToRight(View v);

        public void onRightToLeft(View v);
    }

}

Appelez-le depuis votre classe d'activité comme ceci:

yourLayout.setOnTouchListener(new ActivitySwipeDetector(this, your_activity.this));

Et n'oubliez pas d' implémenter SwipeInterface qui vous donnera deux méthodes @override:

    @Override
    public void onLeftToRight(View v) 
    {
        Log.e("TAG", "L to R");
    }

    @Override
    public void onRightToLeft(View v) 
    {
        Log.e("TAG", "R to L");
    }

Je trouve que MAX_OFF_PATH = 5 * vc.getScaledPagingTouchSlop()c'est plus confortable pour un coup de pouce se déplaçant naturellement dans un léger arc.
qix

4

Les gestes sont ces mouvements subtils pour déclencher des interactions entre l'écran tactile et l'utilisateur. Il dure entre la première touche de l'écran et le moment où le dernier doigt quitte la surface.

Android nous fournit une classe appelée GestureDetector à l' aide de laquelle nous pouvons détecter des gestes courants tels que taper vers le bas et vers le haut, glisser verticalement et horizontalement (lancer), appuyer longuement et brièvement, appuyer deux fois, etc. . et attachez-leur des auditeurs.

Faites en sorte que notre classe Activity implémente les interfaces GestureDetector.OnDoubleTapListener (pour la détection de gestes à double appui) et GestureDetector.OnGestureListener et implémentez toutes les méthodes abstraites. Pour plus d'informations. vous pouvez visiter https://developer.android.com/training/gestures/detector.html . Courtoisie

Pour le test de démonstration. GestureDetectorDemo


4

Si vous n'aimez pas créer une classe distincte ou rendre le code complexe,
vous pouvez simplement créer une variable GestureDetector dans OnTouchListener et rendre votre code plus facile

namVyuVar peut être n'importe quel nom de la vue sur laquelle vous devez définir le listeur

namVyuVar.setOnTouchListener(new View.OnTouchListener()
{
    @Override
    public boolean onTouch(View view, MotionEvent MsnEvtPsgVal)
    {
        flingActionVar.onTouchEvent(MsnEvtPsgVal);
        return true;
    }

    GestureDetector flingActionVar = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener()
    {
        private static final int flingActionMinDstVac = 120;
        private static final int flingActionMinSpdVac = 200;

        @Override
        public boolean onFling(MotionEvent fstMsnEvtPsgVal, MotionEvent lstMsnEvtPsgVal, float flingActionXcoSpdPsgVal, float flingActionYcoSpdPsgVal)
        {
            if(fstMsnEvtPsgVal.getX() - lstMsnEvtPsgVal.getX() > flingActionMinDstVac && Math.abs(flingActionXcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Right to Left fling

                return false;
            }
            else if (lstMsnEvtPsgVal.getX() - fstMsnEvtPsgVal.getX() > flingActionMinDstVac && Math.abs(flingActionXcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Left to Right fling

                return false;
            }

            if(fstMsnEvtPsgVal.getY() - lstMsnEvtPsgVal.getY() > flingActionMinDstVac && Math.abs(flingActionYcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Bottom to Top fling

                return false;
            }
            else if (lstMsnEvtPsgVal.getY() - fstMsnEvtPsgVal.getY() > flingActionMinDstVac && Math.abs(flingActionYcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Top to Bottom fling

                return false;
            }
            return false;
        }
    });
});

3

A tous: n'oubliez pas le cas MotionEvent.ACTION_CANCEL:

il appelle à 30% des balayages sans ACTION_UP

et son égal à ACTION_UP dans ce cas


2

J'ai intégré une classe plus générique, j'ai pris la classe de Tomas et ajouté une interface qui envoie des événements à votre activité ou fragment. il enregistrera l'écouteur sur le constructeur, alors assurez-vous d'implémenter l'interface ou une ClassCastException sera approfondie. l'interface renvoie l'un des quatre derniers int définis dans la classe et renverra la vue sur laquelle elle a été activée.

import android.app.Activity;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class SwipeDetector implements View.OnTouchListener{

    static final int MIN_DISTANCE = 100;
    private float downX, downY, upX, upY;
    public final static int RIGHT_TO_LEFT=1;
    public final static int LEFT_TO_RIGHT=2;
    public final static int TOP_TO_BOTTOM=3;
    public final static int BOTTOM_TO_TOP=4;
    private View v;

    private onSwipeEvent swipeEventListener;


    public SwipeDetector(Activity activity,View v){
        try{
            swipeEventListener=(onSwipeEvent)activity;
        }
        catch(ClassCastException e)
        {
            Log.e("ClassCastException",activity.toString()+" must implement SwipeDetector.onSwipeEvent");
        } 
        this.v=v;
    }
    public SwipeDetector(Fragment fragment,View v){
        try{
            swipeEventListener=(onSwipeEvent)fragment;
        }
        catch(ClassCastException e)
        {
            Log.e("ClassCastException",fragment.toString()+" must implement SwipeDetector.onSwipeEvent");
        } 
        this.v=v;
    }


    public void onRightToLeftSwipe(){   
        swipeEventListener.SwipeEventDetected(v,RIGHT_TO_LEFT);
    }

    public void onLeftToRightSwipe(){   
        swipeEventListener.SwipeEventDetected(v,LEFT_TO_RIGHT);
    }

    public void onTopToBottomSwipe(){   
        swipeEventListener.SwipeEventDetected(v,TOP_TO_BOTTOM);
    }

    public void onBottomToTopSwipe(){
        swipeEventListener.SwipeEventDetected(v,BOTTOM_TO_TOP);
    }

    public boolean onTouch(View v, MotionEvent event) {
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

            //HORIZONTAL SCROLL
            if(Math.abs(deltaX) > Math.abs(deltaY))
            {
                if(Math.abs(deltaX) > MIN_DISTANCE){
                    // left or right
                    if(deltaX < 0) 
                    {
                        this.onLeftToRightSwipe();
                        return true;
                    }
                    if(deltaX > 0) {
                        this.onRightToLeftSwipe();
                        return true; 
                    }
                }
                else {
                    //not long enough swipe...
                    return false; 
                }
            }
            //VERTICAL SCROLL
            else 
            {
                if(Math.abs(deltaY) > MIN_DISTANCE){
                    // top or down
                    if(deltaY < 0) 
                    { this.onTopToBottomSwipe();
                    return true; 
                    }
                    if(deltaY > 0)
                    { this.onBottomToTopSwipe(); 
                    return true;
                    }
                }
                else {
                    //not long enough swipe...
                    return false;
                }
            }

            return true;
        }
        }
        return false;
    }
    public interface onSwipeEvent
    {
        public void SwipeEventDetected(View v , int SwipeType);
    }

}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.