Rilevamento dei gesti di lancio sul layout della griglia


1106

Voglio far flingfunzionare il rilevamento dei gesti nella mia applicazione Android.

Quello che ho è un GridLayoutche contiene 9 ImageViews. La fonte può essere trovata qui: Grid Layout di Romain Guys .

Quel file che prendo proviene dall'applicazione Photostream di Romain Guy ed è stato solo leggermente adattato.

Per la semplice situazione del clic, devo solo impostare il valore onClickListenerper ogni ImageViewche aggiungo come principale activityche implementa View.OnClickListener. Sembra infinitamente più complicato implementare qualcosa che riconosce a fling. Presumo che sia perché potrebbe estendersi views?

  • Se la mia attività viene implementata OnGestureListener, non so come impostarla come listener di gesti per Grido le Imageviste che aggiungo.

    public class SelectFilterActivity extends Activity implements
       View.OnClickListener, OnGestureListener { ...
  • Se la mia attività viene implementata, OnTouchListenernon ho alcun onFlingmetodo per override(ha due eventi come parametri che mi consentono di determinare se il fling era degno di nota).

    public class SelectFilterActivity extends Activity implements
        View.OnClickListener, OnTouchListener { ...
  • Se creo un'abitudine View, come GestureImageViewquella estesa ImageView, non so come dire all'attività che si flingè verificato dalla vista. In ogni caso, ho provato questo e i metodi non sono stati chiamati quando ho toccato lo schermo.

Ho davvero solo bisogno di un esempio concreto di questo lavoro attraverso le viste. Cosa, quando e come devo allegarlo listener? Devo essere in grado di rilevare anche i singoli clic.

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

È possibile posizionare una vista trasparente sulla parte superiore dello schermo per catturare le avventure?

Se scelgo di non inflatevisualizzare le visualizzazioni di immagini di figlio da XML, posso passare il GestureDetectorparametro come costruttore a una nuova sottoclasse di ImageViewquella che creo?

Questa è l'attività molto semplice per cui sto cercando di far funzionare il flingrilevamento: SelectFilterActivity (adattato da photostream) .

Ho esaminato queste fonti:

Finora nulla ha funzionato per me e speravo in alcuni suggerimenti.


Come risolvere questo problema? Si prega di rispondere stackoverflow.com/questions/60464912/...
Bishwash

Risposte:


818

Grazie a Code Shogun , il cui codice mi sono adattato alla mia situazione.

Lascia che la tua attività si attui OnClickListenercome al solito:

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

Allega il tuo ascoltatore di gesti a tutte le viste che aggiungi al layout principale;

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

Osserva meravigliato quando i tuoi metodi scavalcati vengono colpiti, sia onClick(View v)dall'attività che onFlingdall'ascoltatore di gesti.

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

La danza post-lancio è facoltativa ma incoraggiata.


109
Grazie per questo codice! È stato molto utile Tuttavia, mi sono imbattuto in una cattura molto molto frustrante mentre cercavo di far funzionare i gesti. Nel mio SimpleOnGestureListener, devo eseguire l'override di OnDown affinché uno qualsiasi dei miei gesti si registri. Può solo tornare vero, ma devo essere definito. PS: Non so se è la mia revisione API o il mio hardware, ma sto usando 1.5 su un HTC Droid Eris.
Cdsboy,

Ho provato il tuo codice e non importa se scorro o faccio clic (con il mouse, perché lavoro nell'emulatore), ottengo sempre un brindisi che ho definito nel metodo onClick, quindi l'emulatore rileva solo i clic, senza scorrere. Perché è così?
lomza,

Ho provato questo codice e non ha funzionato. non riuscivo ancora a scorrere affatto quando applico un ascoltatore onClick a una delle viste figlio all'interno della mia vista galleria
Jonathan,

Iomza: hai provato a mettere le dichiarazioni di rottura e ad esaminare il tuo codice?
IgorGanapolsky,

Complimenti per l'utilizzo di una classe interiore! Approccio molto pulito.
IgorGanapolsky,

211

Una delle risposte sopra menziona la gestione di densità di pixel diverse ma suggerisce di calcolare manualmente i parametri di scorrimento. Vale la pena notare che è possibile ottenere effettivamente valori ragionevoli in scala dal sistema usando 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)

Ho notato che l'uso di questi valori fa sì che la "sensazione" di lancio sia più coerente tra l'applicazione e il resto del sistema.


11
Io uso swipeMinDistance = vc.getScaledPagingTouchSlop()e swipeMaxOffPath = vc.getScaledTouchSlop().
Thomas Ahle,

8
getScaledTouchSlopmi dà pochissimo risultato di offset, goffamente. Ad esempio solo 24 pixel su uno schermo alto 540, è molto difficile tenerlo a portata di dito. : S
WonderCsabo,

148

Lo faccio in modo leggermente diverso e ho scritto una classe di rivelatori extra che implementa il View.onTouchListener

onCreateè semplicemente aggiungerlo al layout più basso in questo modo:

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

dove id.lowestLayout è l'id.xxx per la vista più bassa nella gerarchia del layout e la più bassaLayout è dichiarata come RelativeLayout

E poi c'è l'attuale classe di rilevatori di attività magnetiche:

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

}

Funziona davvero bene per me!


1
Questo in realtà mi ha reso molto più semplice applicare la funzionalità gestuale e ha richiesto un cablaggio "meno": D Grazie @Thomas
nemesisfixx,

5
Sembra un'utile classe di utilità - ma penso che i tuoi quattro metodi su ... swipe () dovrebbero essere interfacce
Someone Somewhere,

2
questi ritorni non dovrebbero essere lì (riga "non consumiamo l'evento"), vero? Disabilita la funzione di scorrimento verticale.
Marek Sebera,

5
in particolare, il metodo onTouch (). in primo luogo, se il delta X non è abbastanza grande, ritorna senza controllare il delta Y. il risultato è che non rileva mai gli swipe sinistra-destra. in secondo luogo, inoltre, non dovrebbe restituire true se non trova alcun colpo. terzo, non dovrebbe tornare vero in azione. questo impedisce a qualsiasi altro ascoltatore come onClick di funzionare.
Jeffrey Blattman,

1
@Piotr non è un problema finché l'oggetto che contiene il riferimento ha lo stesso ambito dell'attività stessa. il problema si verifica quando si mantiene un riferimento a un'attività in un luogo che ha un ambito più ampio dell'attività ... come ad esempio da un membro statico.
Jeffrey Blattman,

94

Ho leggermente modificato e riparato la soluzione di Thomas Fankhauser

L'intero sistema è costituito da due file, SwipeInterface e 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);

}

Rivelatore

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

}

è usato così:

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

E nell'implementazione Activitydevi implementare metodi da SwipeInterface e puoi scoprire su quale evento View Swipe è stato chiamato.

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

L'ho leggermente modificato di nuovo, vedere il v.performClick();, che viene utilizzato per non consumare l'evento su OnClickListener, se impostato sulla stessa vista
Marek Sebera,

Ciao, sono un principiante assoluto quindi questa domanda potrebbe essere davvero ovvia o banale ma per favore rispondi. La parte in cui hai scritto, viene utilizzata come: ActivitySwipeDetector swipe = new ActivitySwipeDetector (this); Questa affermazione farà parte di MainActivity, giusto? Quindi, "this" sarà un'attività di MainActivity. Considerando che il costruttore prende un'istanza di SwipeInterface. Gentilmente aiutami qui. Molte grazie.
Chocolava,

@Chocolava crea una nuova domanda, il commento non è un bel posto da porre in questo modo.
Marek Sebera,

@MarekSebera non funziona con ScrollView & ListView? come gestirli?
Duc Tran,

@silentbang di nuovo, questo non è il posto per porre tali domande. crea una nuova discussione.
Marek Sebera,

65

Il codice del rilevatore di gesti di scorrimento sopra è molto utile! Tuttavia, è possibile rendere agnostica questa densità della soluzione utilizzando i seguenti valori relativi (REL_SWIPE)anziché i valori assoluti(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 per averlo menzionato. Si noti che DensityMetrics.densityDpi è stato introdotto nell'API 4. Per compatibilità con le versioni precedenti dell'API 1, utilizzare invece DensityMetrics.density. Ciò quindi modifica il calcolo in SWIPE_MIN_DISTANCE * dm.density.
Thane Anthem,

Dove hai preso il numero 160.0f?
IgorGanapolsky,

developer.android.com/guide/practices/screens_support.html Pixel indipendenti dalla densità (dp) La conversione delle unità dp in pixel dello schermo è semplice: px = dp * (dpi / 160)
paiego

Stavo cercando dappertutto per questo. NESSUN esempio di onFling () su Internet ha questo, che porterà a una scarsa UX. Grazie!
Sandy

160.0f è il derivato dal 160 DPI che è la densità standard su cui si basa DP (pixel indipendenti dalla densità). public static final int DENSITY_MEDIUM Aggiunto in API livello 4 DPI quantizzato standard per schermi a media densità. Valore costante: 160 (0x000000a0)
paiego

35

La mia versione della soluzione proposta da Thomas Fankhauser e Marek Sebera (non gestisce i colpi verticali):

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

}

qualcuno può dirmi come chiamare la classe. ActivitySwipeDetector swipe = new ActivitySwipeDetector (questo); ovviamente sta dando errore, in quanto tale costruttore. Devo dare ActivitySwipeDetector swipe = new ActivitySwipeDetector (this, null);
abdfahim,

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

25

Questa domanda è piuttosto vecchia e nel luglio 2011 Google ha rilasciato il pacchetto di compatibilità, revisione 3) che include quello ViewPagerche funziona con Android 1.6 e versioni successive. Le GestureListenerrisposte pubblicate per questa domanda non sono molto eleganti su Android. Se stai cercando il codice utilizzato per passare da una foto all'altra nella Galleria Android o per cambiare visualizzazione nella nuova app Play Market, lo è sicuramente ViewPager.

Ecco alcuni link per maggiori informazioni:


Un problema con ViewPager è che non hai il controllo dei parametri di distanza e velocità per il gesto di lancio.
Almalkawi,

ViewPager non viene utilizzato nella galleria.
Anthony,

18

C'è un'interfaccia integrata che puoi usare direttamente per tutti i gesti:
Ecco una spiegazione per un utente di livello base: inserisci qui la descrizione dell'immagine Vi sono 2 importazioni facendo attenzione a scegliere che entrambe sono diverse inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine


1
E quali sono i prossimi passi? Come impostare quel listener su una vista particolare? E se questa visione fosse parte di un frammento?
Stan,

16

C'è qualche proposta sul web (e questa pagina) per usare ViewConfiguration. getScaledTouchSlop () per avere un valore in scala del dispositivo per SWIPE_MIN_DISTANCE.

getScaledTouchSlop()è inteso per la distanza "soglia di scorrimento ", non scorrere. La distanza della soglia di scorrimento deve essere inferiore alla distanza della soglia di "oscillazione tra le pagine". Ad esempio, questa funzione restituisce 12 pixel sul mio Samsung GS2 e gli esempi citati in questa pagina sono di circa 100 pixel.

Con API Level 8 (Android 2.2, Froyo), hai getScaledPagingTouchSlop(), inteso per lo scorrimento della pagina. Sul mio dispositivo, restituisce 24 (pixel). Quindi, se sei a livello API <8, penso che "2 * getScaledTouchSlop()" dovrebbe essere la soglia di scorrimento "standard". Ma gli utenti della mia applicazione con schermi piccoli mi hanno detto che era troppo pochi ... Come nella mia applicazione, puoi scorrere verticalmente e cambiare pagina in orizzontale. Con il valore proposto, a volte cambiano pagina invece di scorrere.


13

Anche come un piccolo miglioramento.

Il motivo principale del blocco try / catch è che e1 potrebbe essere nullo per il movimento iniziale. oltre a try / catch, includere un test per null e return. simile al seguente

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

12

Ci sono molte informazioni eccellenti qui. Sfortunatamente gran parte di questo codice di processamento è sparpagliato su vari siti in vari stati di completamento, anche se si potrebbe pensare che questo sia essenziale per molte applicazioni.

Mi sono preso il tempo di creare un ascoltatore di avventura che verifica che siano soddisfatte le condizioni appropriate. Ho aggiunto un listener di page fling che aggiunge ulteriori controlli per garantire che i flings soddisfino la soglia per i flings di pagina. Entrambi questi listener consentono di limitare facilmente le oscillazioni all'asse orizzontale o verticale. Puoi vedere come viene utilizzato in una vista per immagini scorrevoli . Riconosco che le persone qui hanno svolto gran parte della ricerca --- L'ho appena messo in una libreria utilizzabile.

Questi ultimi giorni rappresentano la mia prima pugnalata alla programmazione su Android; aspettiamo molto di più a venire.


Voglio implementare il gesto di scorrimento con 2 dita. Per favore aiutatemi!
Gaurav Arora,

12

Questa è una risposta combinata delle due risposte in alto, se qualcuno desidera un'implementazione funzionante.

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() {}
}

Grazie per un'implementazione davvero buona. Inoltre vorrei suggerire di verificare absDeltaY > minSwipeDelta, absVelocityY > minSwipeVelocity, absVelocityY < maxSwipeVelocitysolo nel caso in cui se minSwipeDelta ! = getScaledTouchSlop , minSwipeVelocity ! = getScaledMinimumFlingVelocity , maxSwipeVelocity ! = getScaledMaximumFlingVelocity , Cioè per controllare solo se questi cosiddetti “default” (intendo getScaledTouchSlop, getScaledMinimumFlingVelocity, getScaledMaximumFlingVelocity) i valori vengono scalati o modificati in base alle proprie proprio desiderio.
Elia12345,

Il punto è che, secondo il codice sorgente , i valori "predefiniti" menzionati sono già controllati da GestureDetector e OnFling viene attivato solo se sono confermati (a proposito, l'attivazione ha luogo solo in caso di ACTION_UP, non ACTION_MOVEo ACTION_POINTER_UP, cioè solo come un risultato del gesto pienamente realizzato). (Non ho controllato altre versioni dell'API, quindi i commenti sono apprezzati).
Elia12345,

11

È possibile utilizzare la libreria droidQuery per gestire lanci, clic, clic lunghi ed eventi personalizzati. L'implementazione si basa sulla mia risposta precedente di seguito, ma droidQuery fornisce una sintassi semplice e chiara :

//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];
        }
    }
});

Risposta originale

Questa risposta utilizza una combinazione di componenti delle altre risposte qui. È costituito dalla SwipeDetectorclasse, che ha un'interfaccia interna per l'ascolto di eventi. Ho anche fornire una RelativeLayoutper mostrare come sovrascrivere un View's onTouchmetodo per consentire entrambi gli eventi di scorrimento e altri eventi rilevati (come clic o clic lunghi).

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
Ho provato a implementarlo in una vista che contiene elementi cliccabili. Quando un colpo inizia su un elemento selezionabile (ad es. Una visualizzazione elenco che ha registrato il listener onItemClick), onTouchEvent non viene mai richiamato. Pertanto, l'utente non può iniziare a scorrere su un elemento cliccabile, il che è un peccato per me e sto ancora cercando di capire come aggirare questo problema, poiché i nostri elementi cliccabili occupano un po 'di spazio di visualizzazione e vogliamo ancora supporto per lo scorrimento per l'intera vista. Se un colpo non ricomincia su un elemento cliccabile, allora funziona perfettamente.
Lo-Tan

@ Lo-Tan, questo si verifica perché il vostro articolo cliccabile è una vista del bambino, ed è quindi in cima della SwipeInterceptorView, per cui il suo scatto è gestita prima. È possibile risolvere questo problema implementando il proprio meccanismo di clic implementando onTouchListeneroppure come soluzione alternativa è possibile ascoltare clic lunghi anziché clic (vedere View.setOnLongClickListener).
Phil

In realtà ci sto provando proprio in questo momento. O possibile annullare l'evento click se iniziano un trascinamento :) Grazie mille.
Lo-Tan,

Una soluzione è quella di collegare il rilevatore di scorrimento a ogni vista della tua app. Un altro è implementare onInterceptTouchEvent in SwipeInterceptorView.
Edward Falk,

7

So che è troppo tardi per rispondere, ma sto ancora pubblicando Swipe Detection per ListView che Come usare Swipe Touch Listener nell'elemento ListView .

Refrence: Exterminator13 (una delle risposte in questa pagina)

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

}

Chiamalo dalla tua classe di attività in questo modo:

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

E non dimenticare di implementare SwipeInterface che ti darà due metodi @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");
    }

Trovo che a MAX_OFF_PATH = 5 * vc.getScaledPagingTouchSlop()sia più comodo per un colpo del pollice che viaggia naturalmente in un leggero arco.
qix,

4

I gesti sono quei movimenti sottili per innescare interazioni tra il touchscreen e l'utente. Dura per il tempo che intercorre tra il primo tocco sullo schermo e il punto in cui l'ultimo dito lascia la superficie.

Android ci offre una classe chiamata GestureDetector che consente di rilevare gesti comuni come toccare verso il basso e verso l'alto, scorrere in verticale e in orizzontale (lanciare), premere a lungo e brevemente, doppio tocco, ecc. . e allega gli ascoltatori.

Rendi la nostra classe Activity implementare le interfacce GestureDetector.OnDoubleTapListener (per il rilevamento dei gesti con doppio tocco) e GestureDetector.OnGestureListener e implementare tutti i metodi astratti. Per maggiori informazioni. puoi visitare https://developer.android.com/training/gestures/detector.html . Cortesia

Per il test dimostrativo. GestureDetectorDemo


4

Se non ti piace creare una classe separata o rendere complesso il codice,
puoi semplicemente creare una variabile GestureDetector all'interno di OnTouchListener e rendere il tuo codice più semplice

namVyuVar può essere qualsiasi nome della vista su cui è necessario impostare il listener

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 tutti: non dimenticare il caso MotionEvent.ACTION_CANCEL:

chiama il 30% di passaggi senza ACTION_UP

ed è uguale a ACTION_UP in questo caso


2

Ho inserito una classe più generica, ho preso la lezione di Tomas e ho aggiunto un'interfaccia che invia eventi alla tua attività o frammento. registrerà il listener sul costruttore, quindi assicurati di implementare l'interfaccia o verrà generata una ClassCastException. l'interfaccia restituisce uno dei quattro int finali definiti nella classe e restituirà la vista su cui è stato attivato.

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

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.