Detecção de gesto de arremesso no layout da grade

1106

Quero que a flingdetecção de gestos funcione no meu aplicativo Android.

O que eu tenho é um GridLayoutque contém 9 ImageViews. A fonte pode ser encontrada aqui: Grid Layout da Romain Guys .

Esse arquivo é do aplicativo Photostream de Romain Guy e foi apenas ligeiramente adaptado.

Para a situação de clique simples, preciso definir apenas o onClickListenerpara cada um ImageViewque adiciono como o principal activityque implementa View.OnClickListener. Parece infinitamente mais complicado implementar algo que reconhece a fling. Presumo que isso é porque pode abranger views?

  • Se minha atividade é implementada OnGestureListener, não sei como defini-lo como ouvinte de gestos para as Gridou as Imagevisualizações que adiciono.

    public class SelectFilterActivity extends Activity implements
       View.OnClickListener, OnGestureListener { ...
    
  • Se minha atividade é implementada OnTouchListener, não tenho um onFlingmétodo para override(ela tem dois eventos como parâmetros, permitindo-me determinar se o caso foi digno de nota).

    public class SelectFilterActivity extends Activity implements
        View.OnClickListener, OnTouchListener { ...
    
  • Se eu criar um costume View, assim GestureImageViewprolongado ImageView, não sei como dizer à atividade que flingocorreu uma a partir da visualização. De qualquer forma, tentei isso e os métodos não foram chamados quando toquei na tela.

Eu realmente só preciso de um exemplo concreto desse trabalho entre pontos de vista. O que, quando e como devo anexar isso listener? Também preciso detectar cliques únicos.

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

É possível colocar uma visão transparente por cima da minha tela para capturar arremessos?

Se eu optar por não inflateexibir as imagens da imagem filha no XML, posso passar o GestureDetectorparâmetro como construtor para uma nova subclasse ImageViewcriada por mim?

Esta é a atividade muito simples para a qual estou tentando fazer a flingdetecção funcionar: SelectFilterActivity (Adaptado do photostream) .

Eu estive olhando para estas fontes:

Nada funcionou para mim até agora e eu estava esperando por algumas dicas.

gav
fonte
Como resolver este problema? Por favor, responda stackoverflow.com/questions/60464912/…
Bishwash

Respostas:

818

Obrigado ao Code Shogun , cujo código eu adaptei à minha situação.

Deixe sua atividade implementar OnClickListenercomo de costume:

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

Anexe seu ouvinte a todas as visualizações adicionadas ao layout principal;

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

Observe com admiração como seus métodos substituídos são atingidos, tanto onClick(View v)da atividade quanto do onFlingouvinte de gestos.

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

A dança pós-arremesso é opcional, mas incentivada.

gav
fonte
109
Obrigado por este código! Foi muito útil. No entanto, encontrei uma captura muito, muito frustrante, enquanto tentava fazer gestos funcionarem. No meu SimpleOnGestureListener, preciso substituir onDown para que qualquer um dos meus gestos seja registrado. Apenas pode retornar true, mas eu tenho que ser definido. PS: Não sei se é minha revisão da API ou meu hardware, mas estou usando o 1.5 em um HTC Droid Eris.
Cdsboy # 6/09
Eu tentei o seu código e não importa se eu deslizo ou clico (com o mouse, porque trabalho no emulador), sempre recebo um Toast definido no método onClick, para que o emulador detecte apenas cliques, sem deslizamentos. Por que é tão?
Lomza 12/05
Eu tentei esse código e não funcionou. ainda não foi capaz de rolar em tudo quando eu aplicar um ouvinte onClick para uma das vistas criança dentro da minha galeria vista
Jonathan
Iomza: você tentou colocar instruções de interrupção e percorrer seu código?
IgorGanapolsky
Parabéns por usar uma classe interna! Abordagem muito limpa.
IgorGanapolsky
211

Uma das respostas acima menciona lidar com diferentes densidades de pixels, mas sugere calcular manualmente os parâmetros de furto. Vale a pena notar que você pode realmente obter valores razoáveis ​​e em escala do sistema usando a 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)

Percebi que o uso desses valores faz com que a "sensação" de arremessar seja mais consistente entre o aplicativo e o restante do sistema.

Xion
fonte
11
Eu uso swipeMinDistance = vc.getScaledPagingTouchSlop()e swipeMaxOffPath = vc.getScaledTouchSlop().
Thomas Ahle
8
getScaledTouchSlopdá-me muito pouco resultado de deslocamento, sem jeito. Por exemplo, apenas 24 pixels em uma tela com 540 pontos de altura, é muito difícil mantê-la ao alcance com o dedo. : S
WonderCsabo
148

Eu faço isso um pouco diferente e escrevi uma classe de detector extra que implementa o View.onTouchListener

onCreateé simplesmente adicioná-lo ao layout mais baixo como este:

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

em que id.lowestLayout é o id.xxx para a exibição mais baixa na hierarquia de layout e o lowerLayout é declarado como RelativeLayout

E há a classe real do detector de furto de atividade:

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 muito bem para mim!

Thomas Fankhauser
fonte
1
Isso realmente fez muito mais fácil para mim para aplicar funcionalidade gesto, e exigiu "menos" fiação: D Graças @Thomas
nemesisfixx
5
Isto parece uma classe de utilitário puro - mas acho que seus quatro em ... furto () métodos devem ser as interfaces
alguém em algum lugar
2
esses retornos não deveriam estar lá (linha "não consumimos o evento"), não é? Desativa o recurso de rolagem vertical.
Marek Sebera
5
especificamente, o método onTouch (). primeiro, se o delta X não for grande o suficiente, ele retornará sem verificar o delta Y. o resultado é que nunca detecta os furtos da esquerda para a direita. segundo, ele também não deve retornar true se passar por não encontrar furto. terceiro, não deve retornar verdadeiro na ação inativa. isso impede que qualquer outro ouvinte como o onClick funcione.
Jeffrey Blattman
1
@Piotr não é um problema, desde que o objeto que contém a referência tenha o mesmo escopo que a atividade em si. o problema ocorre quando você mantém uma referência a uma atividade em um local com um escopo maior que a atividade ... como de um membro estático, por exemplo.
Jeffrey Blattman
94

Modifiquei e consertei levemente a solução de Thomas Fankhauser

Todo o sistema consiste em dois arquivos, 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);

}

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

}

é usado assim:

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

E na implementação, Activityvocê precisa implementar métodos do SwipeInterface e pode descobrir em qual evento View the Swipe foi chamado.

@Override
public void left2right(View v) {
    switch(v.getId()){
        case R.id.swipe_layout:
            // do your stuff here
        break;
    }       
}
Marek Sebera
fonte
Eu ligeiramente modificada de novo, ver o v.performClick();que é usado para não consumir evento para OnClickListener, se definido no mesmo ponto de vista
Marek Sebera
Olá, sou totalmente iniciante, portanto, essa pergunta pode ser realmente óbvia ou trivial, mas responda. A parte em que você escreveu, é usada como: ActivitySwipeDetector swipe = new ActivitySwipeDetector (this); Esta declaração fará parte da MainActivity, correto? Então, "this" será uma atividade de MainActivity. Enquanto o construtor usa uma instância de SwipeInterface. Por favor, me ajude aqui. Muito obrigado.
Chocolava
@Chocolava criar nova pergunta, comentar não é um bom lugar para perguntar assim.
Marek Sebera
@MarekSebera isso não funciona com o ScrollView & ListView? como lidar com eles?
Duc Tran
@ Silentbang novamente, este não é o lugar para fazer essas perguntas. por favor crie um novo tópico de pergunta.
Marek Sebera
65

O código do detector de gestos de furto acima é muito útil! No entanto, você pode desejar agnóstico desta densidade de solução usando os seguintes valores relativos, (REL_SWIPE)em vez dos 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);
paiego
fonte
8
+1 por trazer isso à tona. Observe que DensityMetrics.densityDpi foi introduzido na API 4. Para compatibilidade com versões anteriores da API 1, use DensityMetrics.density. Isso muda o cálculo para ser apenas SWIPE_MIN_DISTANCE * dm.density.
Thane Anthem
Onde você conseguiu o número 160.0f?
IgorGanapolsky
developer.android.com/guide/practices/screens_support.html pixels independente de Densidade (dp) A conversão de unidades DP pixels da tela é simples: px = dp * (dpi / 160)
paiego
Eu estava procurando por isso. Nenhum exemplo de onFling () na Internet possui isso, o que levará a um UX ruim. Obrigado!
Sandy
160.0f é proveniente do 160 DPI, que é a densidade padrão na qual o DP (pixels independentes da densidade) é baseado. public static final int DENSITY_MEDIUM Adicionado no nível 4 da API DPI quantificado padrão para telas de densidade média. Valor constante: 160 (0x000000a0)
paiego
35

Minha versão da solução proposta por Thomas Fankhauser e Marek Sebera (não lida com furtos verticais):

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

}
Exterminador13
fonte
Alguém pode me dizer como ligar para a turma. ActivitySwipeDetector swipe = new ActivitySwipeDetector (this); obviamente está dando erro, pois não existe esse construtor. Devo dar ActivitySwipeDetector swipe = new ActivitySwipeDetector (this, null);
precisa saber é
@AbdullahFahim ActivitySwipeDetector (this, YourActivity.this);
Anton Kashpor 7/04
25

Essa pergunta é antiga e, em julho de 2011, o Google lançou o Pacote de Compatibilidade, revisão 3), que inclui o ViewPagerque funciona com o Android 1.6 em diante. As GestureListenerrespostas postadas para esta pergunta não parecem muito elegantes no Android. Se você está procurando o código usado para alternar entre fotos na Galeria Android ou alternar visualizações no novo aplicativo Play Market, então é definitivamente ViewPager.

Aqui estão alguns links para mais informações:

georgiecasey
fonte
Um problema com o ViewPager é que você não tem controle dos parâmetros de distância e velocidade para o gesto de arremessar.
almalkawi
O ViewPager não é usado na galeria.
Anthony
18

Há uma interface interna que você pode usar diretamente para todos os gestos:
Aqui está uma explicação para um usuário de nível básico: insira a descrição da imagem aqui Existem 2 importações com cuidado ao escolher que ambas são diferentes insira a descrição da imagem aqui insira a descrição da imagem aqui

Viswanath Lekshmanan
fonte
1
E quais são os próximos passos? Como definir esse ouvinte para uma exibição específica? E se essa visão fizer parte de um fragmento?
Stan
16

Há alguma proposta na Web (e nesta página) para usar o ViewConfiguration. getScaledTouchSlop () para ter um valor em escala de dispositivo SWIPE_MIN_DISTANCE.

getScaledTouchSlop()destina-se à distância do "limite de rolagem ", não ao deslize. A distância do limite de rolagem deve ser menor que a distância do limite "oscilação entre a página". Por exemplo, essa função retorna 12 pixels no meu Samsung GS2 e os exemplos citados nesta página são de aproximadamente 100 pixels.

Com a API nível 8 (Android 2.2, Froyo), você tem o getScaledPagingTouchSlop()objetivo de deslizar a página. No meu dispositivo, ele retorna 24 (pixels). Portanto, se você estiver no nível da API <8, acho que "2 * getScaledTouchSlop()" deve ser o limite de furto "padrão". Mas os usuários do meu aplicativo com telas pequenas me disseram que era muito pouco ... Como no meu aplicativo, você pode rolar verticalmente e alterar a página horizontalmente. Com o valor proposto, eles às vezes mudam de página em vez de rolar.

MappaM
fonte
13

Também como um aprimoramento menor.

O principal motivo do bloco try / catch é que e1 pode ser nulo para o movimento inicial. além da tentativa / captura, inclua um teste para nulo e retorno. semelhante ao seguinte

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

Há muitas informações excelentes aqui. Infelizmente, muito desse código de processamento de lançamentos está espalhado por vários sites em vários estados de conclusão, mesmo que alguém pense que isso é essencial para muitos aplicativos.

Eu dediquei um tempo para criar um ouvinte que verifica se as condições apropriadas são atendidas. Adicionei um ouvinte de arremesso de página que adiciona mais verificações para garantir que os arremessos atendam ao limite de arremessos de página. Ambos os ouvintes permitem restringir facilmente os arremessos ao eixo horizontal ou vertical. Você pode ver como é usado em uma exibição para imagens deslizantes . Reconheço que as pessoas aqui fizeram a maior parte da pesquisa - acabei de montá-la em uma biblioteca utilizável.

Esses últimos dias representam minha primeira tentativa de codificação no Android; esperar muito mais por vir.

Garret Wilson
fonte
Quero implementar o gesto de furto através de 2 dedos. Por favor, me ajude!
Gaurav Arora
12

Esta é uma resposta combinada das duas respostas na parte superior, se alguém quiser uma implementação funcional.

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() {}
}
Hai Zhang
fonte
Obrigado por uma implementação muito boa. Além disso gostaria de sugerir a verificar absDeltaY > minSwipeDelta, absVelocityY > minSwipeVelocity, absVelocityY < maxSwipeVelocityapenas em caso se minSwipeDelta ! = getScaledTouchSlop , minSwipeVelocity ! = getScaledMinimumFlingVelocity , maxSwipeVelocity ! = getScaledMaximumFlingVelocity , Ou seja, verificar se os chamados “default” (quero dizer getScaledTouchSlop, getScaledMinimumFlingVelocity, getScaledMaximumFlingVelocity) valores são escalados ou alterado de acordo com sua próprio desejo.
Elia12345
O ponto é que, de acordo com o código-fonte , os valores "padrão" mencionados já são verificados pelo GestureDetector, e o OnFling é acionado apenas se forem confirmados (pela maneira como o acionamento ocorre apenas no caso de ACTION_UP, não ACTION_MOVEou ACTION_POINTER_UP, ou seja, apenas como resultado do gesto plenamente realizado). (Eu não verifiquei outras versões da API, portanto, os comentários são apreciados).
Elia12345
11

Você pode usar a biblioteca droidQuery para lidar com arremessos, cliques, cliques longos e eventos personalizados. A implementação é baseada na minha resposta anterior abaixo, mas o droidQuery fornece uma sintaxe simples e simples:

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

Resposta original

Esta resposta usa uma combinação de componentes das outras respostas aqui. Consiste na SwipeDetectorclasse, que possui uma interface interna para escutar eventos. Também forneço um RelativeLayoutpara mostrar como substituir Viewo onTouchmétodo de uma para permitir eventos de furto e outros eventos detectados (como cliques ou cliques longos).

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;
    }
}
Phil
fonte
1
Tentei implementar isso em uma exibição que contém elementos clicáveis. Quando um furto começa com um elemento clicável (por exemplo, uma exibição de lista que tenha o ouvinte onItemClick registrado), o onTouchEvent nunca será chamado. Assim, o usuário não pode iniciar um furto sobre um elemento clicável, o que é lamentável para mim e ainda estou tentando descobrir como contornar isso, pois nossos elementos clicáveis ​​ocupam bastante espaço de visualização e ainda queremos suporte para furto. para a visualização inteira. Se um furto não começar com um elemento clicável, ele funcionará perfeitamente.
Lo-Tan
@ Lo-Tan, isso ocorre porque o item clicável é uma visualização filho e, portanto, fica em cima do SwipeInterceptorView, portanto, o clique é tratado primeiro. Você pode corrigir isso implementando seu próprio mecanismo de clique, implementando onTouchListenerou, como solução alternativa, pode ouvir cliques longos em vez de cliques (consulte View.setOnLongClickListener).
Phil
Na verdade, estou tentando isso no momento. Ou possível cancelar o evento click se eles começarem a arrastar :) Muito obrigado.
Lo-Tan
Uma solução é conectar o detector de furto a todas as visualizações no seu aplicativo. Outra é implementar onInterceptTouchEvent em seu SwipeInterceptorView.
Edward Falk
7

Sei que é tarde demais para responder, mas ainda estou publicando a Detecção de furto no ListView que Como usar o Swipe Touch Listener no Item do ListView .

Refrence: Exterminator13 (uma das respostas nesta página)

Faça um 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);
    }

}

Chame-o da sua classe de atividades assim:

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

E não se esqueça de implementar o SwipeInterface, que fornecerá dois 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");
    }
Sagar Shah
fonte
Acho que a MAX_OFF_PATH = 5 * vc.getScaledPagingTouchSlop()é mais confortável para um deslizar do polegar naturalmente viajando em um leve arco.
Qix
4

Gestos são movimentos sutis para acionar interações entre a tela sensível ao toque e o usuário. Dura o tempo entre o primeiro toque na tela e o ponto em que o último dedo sai da superfície.

Android nos fornece uma classe chamada GestureDetector usando o que podemos detectar gestos comuns, como tocar baixo e para cima, passando verticalmente e horizontalmente (aventura), de longo e curto imprensa, torneiras duplas, etc . e anexar ouvintes a eles.

Faça nossa classe Activity implementar GestureDetector.OnDoubleTapListener (para detecção de gesto com toque duplo) e GestureDetector.OnGestureListener interfaces e implementar todos os métodos abstratos. Para obter mais informações. você pode visitar https://developer.android.com/training/gestures/detector.html . Cortesia

Para teste de demonstração. GestureDetectorDemo

IntelliJ Amiya
fonte
4

Se você não deseja criar uma classe separada ou tornar o código complexo,
basta criar uma variável GestureDetector dentro do OnTouchListener e tornar seu código mais fácil

namVyuVar pode ser qualquer nome da Visualização na qual você precisa definir o 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;
        }
    });
});
Sujay UN
fonte
3

Para todos: não se esqueça do caso MotionEvent.ACTION_CANCEL:

chama em 30% furtos sem ACTION_UP

e é igual a ACTION_UP neste caso

djdance
fonte
2

Anexei uma classe mais genérica, peguei a classe de Tomas e adicionei uma interface que envia eventos para sua atividade ou fragmento. ele registrará o ouvinte no construtor, portanto, certifique-se de implementar a interface ou uma ClassCastException será exibida. a interface retorna um dos quatro int finais definidos na classe e retornará a visualização na qual foi ativada.

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

}
Gal Rom
fonte