Fling detección de gestos en el diseño de la cuadrícula

Quiero tener fling detección de gestos funcionando en mi aplicación de Android.

Lo que tengo es un GridLayout que contiene 9 ImageViews. La fuente se puede encontrar aquí: Diseño de cuadrícula de Romain Guys.

Ese archivo que tomo es de Romain Guy. Aplicación Photostream y solo se ha adaptado ligeramente.

Para la situación de clic simple, solo necesito configurar el onClickListener para cada ImageView Agrego para ser el principal activity que implementa View.OnClickListener. Parece infinitamente más complicado implementar algo que reconozca un fling. Supongo que esto se debe a que puede abarcar views?

  • Si mi actividad implementa OnGestureListener No sé cómo establecer eso como el oyente de gestos para el Grid o las Image vistas que agrego.

    public class SelectFilterActivity extends Activity implements
       View.OnClickListener, OnGestureListener { ...
    
  • Si mi actividad implementa OnTouchListener entonces no tengo onFling método para override (tiene dos eventos como parámetros que me permiten determinar si la aventura fue digna de mención).

    public class SelectFilterActivity extends Activity implements
        View.OnClickListener, OnTouchListener { ...
    
  • Si hago una costumbre View, me gusta GestureImageView que se extiende ImageView No sé cómo decirle a la actividad que un fling ha ocurrido desde la vista. En cualquier caso, probé esto y los métodos no fueron llamados cuando toqué la pantalla.

Realmente solo necesito un ejemplo concreto de cómo funciona esto en todas las vistas. ¿Qué, cuándo y cómo debo adjuntar esto? listener? También necesito poder detectar clics individuales.

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

¿Es posible colocar una vista transparente en la parte superior de mi pantalla para capturar aventuras?

Si elijo no hacerlo inflate las vistas de imagen de mi hijo desde XML ¿puedo pasar el GestureDetector como un parámetro de constructor para una nueva subclase de ImageView que yo creo?

Esta es la actividad muy simple que estoy tratando de obtener fling detección para trabajar: SelectFilterActivity (adaptado de la secuencia de fotos).

He estado mirando estas fuentes:

Nada me ha funcionado hasta ahora y esperaba algunos consejos.

preguntado el 01 de junio de 09 a las 23:06

¿Cómo resolver este problema? Por favor conteste stackoverflow.com/questions/60464912/… -

18 Respuestas

Gracias a Código Shogun, cuyo código adapté a mi situación.

Deje que su actividad se implementeOnClickListener como siempre:

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

Adjunte su oyente de gestos a todas las vistas que agregue al diseño principal;

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

Observe con asombro cómo se golpean sus métodos anulados, tanto el onClick(View v) de la actividad y la onFling del oyente de gestos.

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

El baile posterior al 'fling' es opcional pero se recomienda.

contestado el 11 de mayo de 20 a las 10:05

¡Gracias por este código! Fue muy útil. Sin embargo, me encontré con una trampa muy frustrante mientras intentaba que los gestos funcionaran. En mi SimpleOnGestureListener, tengo que anular onDown para que se registre cualquiera de mis gestos. Simplemente puede devolver verdadero pero tengo que ser definido. PD: No sé si es mi revisión de API o mi hardware, pero estoy usando 1.5 en un HTC Droid Eris. - Cdsboy

Probé su código y no importa si deslizo o hago clic (con mi mouse, porque trabajo en el emulador), siempre obtengo un Toast que definí en el método onClick, por lo que el emulador detecta solo clics, sin deslizamientos. ¿Por que es esto entonces? - lomza

Probé este código y no funcionó. todavía no pude desplazarme en absoluto cuando aplico un oyente onClick a una de las vistas secundarias dentro de mi vista de galería - Jonathan

Iomza: ¿intentaste poner declaraciones de interrupción y revisar tu código? - IgorGanapolsky

¡Felicitaciones por usar una clase interna! Enfoque muy limpio. - IgorGanapolsky

Una de las respuestas anteriores menciona el manejo de diferentes densidades de píxeles, pero sugiere calcular los parámetros de deslizamiento a mano. Vale la pena señalar que realmente puede obtener valores razonables escalados del sistema utilizando ViewConfiguration clase:

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)

Noté que el uso de estos valores hace que la "sensación" de aventura sea más consistente entre la aplicación y el resto del sistema.

contestado el 29 de mayo de 13 a las 17:05

yo suelo swipeMinDistance = vc.getScaledPagingTouchSlop() y los swipeMaxOffPath = vc.getScaledTouchSlop(). - Thomas Ahle

getScaledTouchSlop me da muy poco resultado de compensación, torpemente. Por ejemplo, solo 24 píxeles en una pantalla de 540 de altura, es muy difícil mantenerlo dentro del alcance con el dedo. :S - WonderCsabo

Lo hago un poco diferente y escribí una clase de detector adicional que implementa el View.onTouchListener

onCreatees simplemente agregarlo al diseño más bajo de esta manera:

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

donde id.lowestLayout es el id.xxx para la vista más baja en la jerarquía de diseño y lowerLayout se declara como RelativeLayout

Y luego está la clase de detector de deslizamiento de actividad real:

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

}

¡Funciona muy bien para mí!

Respondido 21 Jul 15, 05:07

De hecho, esto me facilitó mucho la aplicación de la funcionalidad de gestos y requirió "menos" cableado: D Gracias @Thomas - némesisfixx

Esto parece una clase de utilidad ordenada, pero creo que sus cuatro métodos en ... swipe () deberían ser interfaces - Alguien en alguna parte

estos retornos no deberían estar ahí (línea "no consumimos el evento"), ¿no es así? Desactiva la función de desplazamiento vertical. - Marek Sebera

específicamente, el método onTouch (). Primero, si el delta X no es lo suficientemente grande, regresa sin verificar el delta Y. el resultado es que nunca detecta los deslizamientos de izquierda a derecha. en segundo lugar, tampoco debería volver a ser verdadero si no encuentra ningún deslizamiento. tercero, no debería volver a ser verdadero en la acción hacia abajo. esto evita que cualquier otro oyente como onClick funcione. - Jeffrey Blattman

@Piotr no es un problema siempre que el objeto que contiene la referencia tenga el mismo alcance que la actividad en sí. el problema ocurre cuando mantiene una referencia a una actividad en un lugar que tiene un alcance mayor que la actividad ... como de un miembro estático, por ejemplo. - Jeffrey Blattman

Modifiqué y reparé ligeramente la solución de Thomas Fankhauser

Todo el sistema consta de dos archivos, SwipeInterface y los 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);

}

Detector

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

}

se usa así:

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

Y en la implementación Activity necesitas implementar métodos desde SwipeInterfacey puede averiguar en qué Vista Deslizar evento fue llamado.

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

contestado el 23 de mayo de 17 a las 15:05

Lo modifiqué ligeramente de nuevo, vea el v.performClick();, que se usa para no consumir eventos en OnClickListener, si se configura en la misma vista - Marek Sebera

Hola, soy un principiante total, así que esta pregunta puede ser realmente obvia o trivial, pero por favor responde. La parte donde ha escrito, se usa como: ActivitySwipeDetector swipe = new ActivitySwipeDetector (esto); Esta declaración será parte de MainActivity, ¿correcto? Entonces, "esto" será una actividad de MainActivity. Mientras que el constructor toma una instancia de SwipeInterface. Amablemente ayúdame aquí. Muchas gracias. - Chocolava

@Chocolava crea una nueva pregunta, comentar no es un buen lugar para preguntar así. - Marek Sebera

@MarekSebera, ¿esto no funciona con ScrollView y ListView? como manejarlos? - Duc Tran

@silentbang nuevamente, este no es el lugar para hacer tales preguntas. por favor cree un nuevo hilo de preguntas. - Marek Sebera

El código del detector de gestos de deslizamiento anterior es muy útil. Sin embargo, es posible que desee hacer que esta densidad de solución sea agnóstica mediante el uso de los siguientes valores relativos (REL_SWIPE) en lugar de los valores absolutos (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);

Respondido el 22 de junio de 12 a las 15:06

+1 por mencionar esto. Tenga en cuenta que DensityMetrics.densityDpi se introdujo en la API 4. Para obtener compatibilidad con versiones anteriores de la API 1, utilice DensityMetrics.density en su lugar. Esto luego cambia el cálculo para que sea solo SWIPE_MIN_DISTANCE * dm.density. - Himno de Thane

¿De dónde sacaste el número 160.0f? - IgorGanapolsky

developer.android.com/guide/practices/screens_support.html Píxeles independientes de la densidad (dp) La conversión de unidades dp a píxeles de pantalla es simple: px = dp * (dpi / 160) - paiego

Estaba buscando esto por todas partes. NINGÚN ejemplo de onFling () en Internet tiene esto, lo que conducirá a una mala UX. ¡Gracias! - Sandy

160.0f proviene de 160 DPI, que es la densidad estándar en la que se basa DP (píxeles independientes de la densidad). public static final int DENSITY_MEDIUM Agregado en API nivel 4 DPI cuantificado estándar para pantallas de densidad media. Valor constante: 160 (0x000000a0) - paiego

Mi versión de solución propuesta por Thomas Fankhauser y los Marek Sebera (no admite golpes verticales):

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

}

contestado el 23 de mayo de 17 a las 15:05

¿Alguien puede decirme cómo llamar a la clase? ActivitySwipeDetector swipe = new ActivitySwipeDetector (esto); obviamente está dando error, ya que no existe tal constructor. ¿Debo darle ActivitySwipeDetector swipe = new ActivitySwipeDetector (esto, nulo); - abdfahim

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

Esta pregunta es un poco vieja y en julio de 2011 Google publicó el Paquete de compatibilidad, revisión 3) que incluye el ViewPager que funciona con Android 1.6 en adelante. La GestureListener las respuestas publicadas para esta pregunta no se sienten muy elegantes en Android. Si está buscando el código utilizado para cambiar entre fotos en la Galería de Android o cambiar de vista en la nueva aplicación Play Market, definitivamente es ViewPager.

Aquí hay algunos enlaces para obtener más información:

respondido 11 nov., 17:11

Un problema con ViewPager es que no tiene control de los parámetros de distancia y velocidad para el gesto de lanzar. - almalkawi

ViewPager no se usa en la galería. - Antonio

Hay una interfaz incorporada que puede usar directamente para todos los gestos:
Aquí hay una explicación para un usuario de nivel básico: enter image description here Hay 2 importaciones, tenga cuidado al elegir que ambas son diferentes. enter image description here enter image description here

Respondido el 22 de Septiembre de 17 a las 02:09

¿Y cuáles son los próximos pasos? ¿Cómo configurar ese oyente para una vista en particular? ¿Y si esta vista es parte de un fragmento? - Stan

Hay una propuesta en la web (y esta página) para usar ViewConfiguration.getScaledTouchSlop () tener un valor escalado por dispositivo para SWIPE_MIN_DISTANCE.

getScaledTouchSlop() está destinado a "desplazamiento distancia de umbral ", no de deslizamiento. La distancia de umbral de desplazamiento tiene que ser menor que una distancia de umbral de" oscilación entre páginas ". Por ejemplo, esta función devuelve 12 píxeles en mi Samsung GS2, y los ejemplos citados en esta página son alrededor de 100 píxeles.

Con API Nivel 8 (Android 2.2, Froyo), tienes getScaledPagingTouchSlop(), diseñado para deslizar la página. En mi dispositivo, devuelve 24 (píxeles). Por lo tanto, si tiene un nivel de API <8, creo que "2 * getScaledTouchSlop()"debería ser el umbral de deslizamiento" estándar ". Pero los usuarios de mi aplicación con pantallas pequeñas me dijeron que era muy poco ... Al igual que en mi aplicación, puede desplazarse verticalmente y cambiar de página horizontalmente. Con el valor propuesto, a veces cambiar de página en lugar de desplazarse.

respondido 12 nov., 17:21

También como mejora menor.

La razón principal del bloque try / catch es que e1 podría ser nulo para el movimiento inicial. además del try / catch, incluya una prueba para null y return. similar al siguiente

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

Respondido 13 Abr '11, 01:04

Aquí hay mucha información excelente. Desafortunadamente, gran parte de este código de procesamiento de lanzamientos está disperso en varios sitios en varios estados de finalización, aunque uno pensaría que esto es esencial para muchas aplicaciones.

Me he tomado el tiempo de crear un oyente de lanzamiento que verifica que se cumplan las condiciones adecuadas. He agregado un oyente de lanzamiento de página eso agrega más controles para garantizar que los lanzamientos llenen el umbral de los cambios de página. Ambos oyentes le permiten restringir fácilmente los lanzamientos al eje horizontal o vertical. Puedes ver cómo se usa en un ver para deslizar imágenes. Reconozco que la gente de aquí ha hecho la mayor parte de la investigación; acabo de ponerlo en una biblioteca utilizable.

Estos últimos días representan mi primer intento de codificar en Android; suponer mucho más venir.

respondido 24 mar '13, 21:03

Quiero implementar el gesto de deslizar con 2 dedos. ¡Por favor, ayúdame! - Gaurav Arora

Esta es una respuesta combinada de las dos respuestas en la parte superior, si alguien quiere una implementación que funcione.

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

Respondido el 07 de diciembre de 14 a las 10:12

Gracias por una implementación realmente buena. Además, sugeriría verificar absDeltaY > minSwipeDelta, absVelocityY > minSwipeVelocity, absVelocityY < maxSwipeVelocity solo en caso de que minSwipeDelta != getScaledTouchSlop, minSwipeVelocity != getScaledMinimumFlingVelocity, maxSwipeVelocity != getScaledMaximumFlingVelocity, es decir, para verificar solo si estos valores llamados “predeterminados” (me refiero a getScaledTouchSlop, getScaledMinimumFlingVelocity, getScaledMaximumFlingVelocity) se escalan o cambian según su propio deseo. - Eliaxnumx

El punto es que de acuerdo con el código fuente, GestureDetector ya verifica los valores "predeterminados" mencionados, y OnFling solo se activa si se confirman (por cierto, la activación se produce solo en caso de ACTION_UPno, ACTION_MOVE or ACTION_POINTER_UP, es decir, sólo como resultado del gesto plenamente realizado). (No he comprobado otras versiones de API, por lo que se agradecen los comentarios). - Eliaxnumx

Puede utilizar el droidQuery biblioteca para manejar aventuras, clics, clics largos y eventos personalizados. La implementación se basa en mi respuesta anterior a continuación, pero droidQuery proporciona una sintaxis simple y elegante:

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

Respuesta original

Esta respuesta usa una combinación de componentes de las otras respuestas aquí. Consiste en el SwipeDetector class, que tiene una interfaz interna para escuchar eventos. También proporciono un RelativeLayout para mostrar cómo anular un View's onTouch método para permitir tanto eventos de deslizamiento como otros eventos detectados (como clics o clics largos).

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

Vista del interceptor de deslizamiento

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

Respondido 21 Jul 15, 05:07

Intenté implementar esto en una vista que contiene elementos en los que se puede hacer clic. Cuando un deslizamiento comienza sobre un elemento en el que se puede hacer clic (por ejemplo, una vista de lista que tiene un oyente de onItemClick registrado), nunca se invoca onTouchEvent. Por lo tanto, el usuario no puede iniciar un deslizamiento sobre un elemento en el que se puede hacer clic, lo cual es desafortunado para mí y todavía estoy tratando de averiguar cómo solucionarlo, ya que nuestros elementos en los que se puede hacer clic ocupan bastante espacio de visualización y todavía queremos soporte para deslizar. para toda la vista. Si un deslizamiento no comienza sobre un elemento en el que se puede hacer clic, entonces funciona perfectamente. - Lo-Tan

@ Lo-Tan, esto ocurre porque el elemento en el que se puede hacer clic es una vista secundaria y, por lo tanto, en la parte superior de los SwipeInterceptorView, por lo que su clic se maneja primero. Puede solucionar esto implementando su propio mecanismo de clic implementando onTouchListenero como solución alternativa, puede escuchar clics largos en lugar de clics (consulte View.setOnLongClickListener). - Phil

De hecho, lo estoy intentando en este momento. O es posible cancelar el evento de clic si comienzan a arrastrar :) Muchas gracias. - Lo-Tan

Una solución es conectar el detector de deslizamiento a todas las vistas de su aplicación. Otro es implementar onInterceptTouchEvent en su SwipeInterceptorView. - Edward Falk

Sé que es demasiado tarde para responder, pero aún estoy publicando Detección de deslizamiento para ListView que como usar Deslizar el oyente táctil en el elemento ListView.

Refrence: Exterminator13 (una de las respuestas en esta página)

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

}

Llámalo desde tu clase de actividad así:

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

Y no te olvides de implementar SwipeInterface que le dará dos métodos @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");
    }

Respondido 06 ago 14, 13:08

Encuentro que un MAX_OFF_PATH = 5 * vc.getScaledPagingTouchSlop() es más cómodo para un deslizamiento del pulgar que se desplaza naturalmente en un ligero arco. - qix

Los gestos son esos movimientos sutiles para desencadenar interacciones entre la pantalla táctil y el usuario. Dura el tiempo que transcurre entre el primer toque en la pantalla y el momento en que el último dedo sale de la superficie.

Android nos proporciona una clase llamada GestureDetector con el cual podemos detectar gestos comunes como tocar hacia abajo y hacia arriba, deslizar vertical y horizontalmente (arrojar), pulsación larga y corta, pulsaciones dobles, etc.. y adjuntar a los oyentes a ellos.

Haz nuestra actividad implementación de clase GestureDetector.OnDoubleTapListener (para la detección de gestos de doble toque) y Interfaces GestureDetector.OnGestureListener e implementar todos los métodos abstractos. Para obtener más información. puedes visitar https://developer.android.com/training/gestures/detector.html . Cortesía

Para prueba de demostración.GestureDetectorDemo

Respondido 23 Jul 15, 13:07

Si no le gusta crear una clase separada o hacer que el código sea complejo,
Puede crear una variable GestureDetector dentro de OnTouchListener y hacer que su código sea más fácil

namVyuVar puede ser cualquier nombre de la Vista en la que necesita configurar el listner

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

contestado el 30 de mayo de 17 a las 21:05

A todos: no se olviden de caso MotionEvent.ACTION_CANCEL:

llama en un 30% de deslizamientos sin ACTION_UP

y es igual a ACTION_UP en este caso

Respondido el 28 de diciembre de 13 a las 16:12

Agregué una clase más genérica, tomé la clase de Tomas y agregué una interfaz que envía eventos a su actividad o fragmento. registrará el oyente en el constructor, así que asegúrese de implementar la interfaz o se ejecutará una ClassCastException. la interfaz devuelve uno de los cuatro int finales definidos en la clase y devolverá la vista en la que se activó.

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

}

Respondido 31 Jul 14, 09:07

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.