Обнаружение жестов на макете сетки

1106

я хочу получить fling в моем приложении для Android работало распознавание жестов.

То, что у меня есть GridLayout, содержит 9 ImageViewс. Источник может быть найден здесь: Romain Guys's Grid Layout .

Этот файл я взял из приложения Фотопотока Ромена Гая и только слегка адаптирован.

Для простой ситуации щелчка мне нужно только установить onClickListenerдля каждого, что ImageViewя добавлю, чтобы быть основным, activityкоторый реализует View.OnClickListener. Кажется, что бесконечно сложнее реализовать что-то, что распознает fling. Я предполагаю, что это потому, что это может охватывать views?

  • Если моя деятельность реализуется, OnGestureListenerя не знаю, как установить это в качестве прослушивателя жестов для Gridили Imageпредставлений, которые я добавляю.

    public class SelectFilterActivity extends Activity implements
       View.OnClickListener, OnGestureListener { ...
  • Если моя деятельность реализуется, OnTouchListenerто у меня нет onFlingметода override(у него есть два события в качестве параметров, позволяющих мне определить, заслуживает ли бросок внимания).

    public class SelectFilterActivity extends Activity implements
        View.OnClickListener, OnTouchListener { ...
  • Если я создаю обычай View, как GestureImageViewэто расширяется, ImageViewя не знаю, как определить действие, flingкоторое произошло в представлении. В любом случае, я попробовал это, и методы не были вызваны, когда я касался экрана.

Мне просто нужен конкретный пример этой работы через взгляды. Что, когда и как мне это прикрепить listener? Я должен быть в состоянии обнаружить отдельные щелчки также.

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

Можно ли положить прозрачный вид поверх моего экрана, чтобы захватить броски?

Если я выберу не inflateмои дочерние представления изображений из XML, могу ли я передать в GestureDetectorкачестве параметра конструктора новый подклассImageView который я создаю?

Это очень простое действие, для которого я пытаюсь заставить flingработать детектор : SelectFilterActivity (адаптировано из фотопотока) .

Я смотрел на эти источники:

До сих пор у меня ничего не получалось, и я надеялся на несколько указателей.

гав
источник
Как решить эту проблему? Пожалуйста, ответьте на stackoverflow.com/questions/60464912/…
Bishwash

Ответы:

818

Благодаря Код Сёгун , код которого я адаптировал к моей ситуации.

Пусть ваша деятельность реализуется OnClickListenerкак обычно:

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

Прикрепите свой слушатель жестов ко всем представлениям, которые вы добавляете в основной макет;

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

С трепетом наблюдайте, как поражаются ваши переопределенные методы, как onClick(View v)активности, так и onFlingслушателя жестов.

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

Танец пост-броска не обязателен, но приветствуется.

гав
источник
109
Спасибо за этот код! Это было очень полезно. Тем не менее, я столкнулся с одним очень очень неприятным уловом, пытаясь заставить жесты работать. В моем SimpleOnGestureListener, я должен переопределить onDown для любого из моих жестов, чтобы зарегистрироваться. Это может просто вернуть true, но я должен быть определен. PS: я не знаю, моя ли это версия api или мое оборудование, но я использую 1.5 на HTC Droid Eris.
Cdsboy
Я попробовал ваш код, и не имеет значения, если я проведу пальцем или щелкну мышью (потому что я работаю в эмуляторе), я всегда получаю Toast, который я определил в методе onClick, поэтому эмулятор обнаруживает только щелчки, без пролистывания. Почему это так?
Ломжа
Я попробовал этот код, и он не работал. все еще не мог прокрутить вообще, когда я применяю слушателя onClick к одному из дочерних представлений в моем представлении галереи
Джонатан
Iomza: ты пробовал ставить операторы break и проходить через твой код?
Игорь Ганапольский
Престижность за использование внутреннего класса! Очень чистый подход.
Игорь Ганапольский
211

В одном из приведенных выше ответов упоминается обработка с различной плотностью пикселей, но предлагается вычислять параметры свайпа вручную. Стоит отметить, что вы можете получить масштабированные, разумные значения из системы, используя ViewConfigurationкласс:

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)

Я заметил, что использование этих значений приводит к тому, что «чувство» сброса становится более согласованным между приложением и остальной частью системы.

Xion
источник
11
Я использую swipeMinDistance = vc.getScaledPagingTouchSlop()и swipeMaxOffPath = vc.getScaledTouchSlop().
Томас Але
8
getScaledTouchSlopнеловко дает мне очень маленький результат смещения Например, всего 24 пикселя на экране с высотой 540, очень сложно держать его в пределах пальца. : S
WonderCsabo
148

Я делаю это немного по-другому, и написал дополнительный класс детектора, который реализует View.onTouchListener

onCreateпросто добавьте его в самый низкий макет, как это:

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

где id.lowestLayout - это id.xxx для представления, самого низкого в иерархии макета, а самое низкое значение объявлено как RelativeLayout

И затем есть фактический класс детектор движения салфетки:

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

}

Работает действительно хорошо для меня!

Томас Фанкхаузер
источник
1
Это фактически облегчило мне применение функциональности жестов и потребовало "менее" разводки: D Спасибо @Thomas
nemesisfixx
5
Это похоже на полезный служебный класс - но я думаю, что ваши четыре метода swipe () должны быть интерфейсами
Someone Somewhere
2
этих возвращений не должно быть (строка «мы не потребляем событие»), не так ли? Отключает функцию вертикальной прокрутки.
Марек Себера
5
в частности, метод onTouch (). Во-первых, если дельта X недостаточно велика, она возвращается без проверки дельты Y. В результате никогда не обнаруживаются пролистывания влево-вправо. во-вторых, он также не должен возвращать истину, если он падает, не найдя пролистывания. в-третьих, он не должен возвращать истину при действии вниз. это препятствует работе любого другого слушателя, такого как onClick.
Джеффри Блатман
1
@Piotr - это не проблема, если объект, содержащий ссылку, находится в той же области действия, что и само действие. проблема возникает, когда вы сохраняете ссылку на действие в месте, которое имеет большую область действия, чем действие ... например, из статического члена.
Джеффри Блатман
94

Я слегка доработал и отремонтировал решение от Thomas Fankhauser

Вся система состоит из двух файлов, SwipeInterface и 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);

}

детектор

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

}

он используется так:

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

А при реализации Activityвам нужно реализовать методы из SwipeInterface , и вы сможете узнать, по какому вызову вызывалось событие View Swipe .

@Override
public void left2right(View v) {
    switch(v.getId()){
        case R.id.swipe_layout:
            // do your stuff here
        break;
    }       
}
Марек Себера
источник
Я немного изменил его снова, см. v.performClick();, Который используется, чтобы не использовать событие для OnClickListener, если установлено на то же представление
Marek Sebera
Привет, я новичок, поэтому этот вопрос может быть очевидным или тривиальным, но, пожалуйста, ответьте. Часть, в которой вы написали, она используется как: ActivitySwipeDetector swipe = new ActivitySwipeDetector (this); Это утверждение будет частью MainActivity, верно? Тогда «это» будет активностью MainActivity. Принимая во внимание, что конструктор берет экземпляр SwipeInterface. Пожалуйста, помогите мне здесь. Большое спасибо.
Chocolava
@Chocolava создать новый вопрос, комментарий не является хорошим местом, чтобы спросить, как это.
Марек Себера
@MarekSebera это не работает с ScrollView & ListView? как с ними справиться?
Дык Тран
@silentbang еще раз, это не место, чтобы задавать такие вопросы. пожалуйста, создайте новую ветку вопросов.
Марек Себера
65

Приведенный выше код детектора жестов очень полезен! Однако вы можете захотеть сделать эту плотность раствора независимой, используя следующие относительные значения, (REL_SWIPE)а не абсолютные значения(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
источник
8
+1 за то, что поднял это. Обратите внимание, что DensityMetrics.densityDpi был представлен в API 4. Для обратной совместимости с API 1 используйте взамен DensityMetrics.density. Это тогда изменяет вычисление, чтобы быть просто SWIPE_MIN_DISTANCE * dm.density.
Гимн Тана,
Где ты взял номер 160.0f?
Игорь Ганапольский
developer.android.com/guide/practices/screens_support.html Плотно-независимый пиксель (dp) Преобразование единиц dp в пиксели экрана очень просто: px = dp * (dpi / 160)
paiego
Я искал повсюду для этого. Ни один пример onFling () в Интернете не имеет такого, что приведет к плохому UX. Спасибо!
Сэнди
160.0f - это значение 160 DPI, которое является стандартной плотностью, на которой основан DP (независимые от плотности пиксели). public static final int DENSITY_MEDIUM Добавлен в API уровень 4 Стандартное квантованное DPI для экранов средней плотности. Постоянное значение: 160 (0x000000a0)
paiego
35

Моя версия решения, предложенная Томасом Фанкхаузером и Мареком Себерой (не обрабатывает вертикальные пролистывания):

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

}
Exterminator13
источник
Может кто-нибудь, пожалуйста, скажите мне, как позвонить в класс. ActivitySwipeDetector swipe = новый ActivitySwipeDetector (это); очевидно дает ошибку, так как нет такого конструктора. Должен ли я указать ActivitySwipeDetector swipe = new ActivitySwipeDetector (this, null);
abdfahim
@AbdullahFahim ActivitySwipeDetector (это, YourActivity.this);
Антон Кашпор
25

Этот вопрос устарел, и в июле 2011 года Google выпустила пакет совместимости, редакция 3), который включает в себя версию, которая ViewPagerработает с Android 1.6 и выше. В GestureListenerответ отвечал на этот вопрос не чувствую себя очень элегантно на Android. Если вы ищете код, используемый для переключения между фотографиями в Галерее Android или переключения между режимами просмотра в новом приложении Play Market, то это точно ViewPager.

Вот несколько ссылок для получения дополнительной информации:

georgiecasey
источник
Одна проблема с ViewPager заключается в том, что вы не можете контролировать параметры расстояния и скорости для жеста броска.
almalkawi
ViewPager не используется в галерее.
Энтони
18

Есть встроенный интерфейс, который вы можете использовать непосредственно для всех жестов:
Вот объяснение для пользователя базового уровня: введите описание изображения здесь Есть 2 импорта, будьте осторожны при выборе, что оба отличаются введите описание изображения здесь введите описание изображения здесь

Вишванат Лекшманан
источник
1
И каковы дальнейшие шаги? Как настроить этого слушателя на определенный вид? А что если этот вид является частью фрагмента?
Стан
16

В Интернете (и на этой странице) есть предложение использовать ViewConfiguration. getScaledTouchSlop (), чтобы иметь масштабируемое значение для устройства SWIPE_MIN_DISTANCE.

getScaledTouchSlop()предназначен для " прокрутки порога » расстояния, а не смахивания. Пороговое расстояние прокрутки должно быть меньше порогового расстояния для перехода между страницами. Например, эта функция возвращает 12 пикселей на моем Samsung GS2, а примеры, приведенные на этой странице, составляют около 100 пикселей.

С API Level 8 (Android 2.2, Froyo) у вас есть getScaledPagingTouchSlop(), предназначенный для пролистывания страниц. На моем устройстве он возвращает 24 (пикселей). Поэтому, если вы находитесь на уровне API <8, я думаю, что «2 * getScaledTouchSlop()» должно быть «стандартным» порогом прокрутки. Но пользователи моего приложения с маленькими экранами сказали мне, что его было слишком мало ... Как и в моем приложении, вы можете прокручивать по вертикали и менять страницу по горизонтали. С предложенным значением они иногда изменяют страницу вместо прокрутки.

MappaM
источник
13

Также как незначительное улучшение.

Основная причина блока try / catch заключается в том, что e1 может быть нулевым для начального перемещения. в дополнение к try / catch, включите тест на null и return. похоже на следующее

if (e1 == null || e2 == null) return false;
try {
...
} catch (Exception e) {}
return false;
Ной
источник
12

Здесь много отличной информации. К сожалению, большая часть этого кода обработки разбросана по разным сайтам в разных состояниях завершения, хотя можно подумать, что это важно для многих приложений.

Я нашел время, чтобы создать прослушиватель, который проверяет, что соответствующие условия выполнены. Я добавил прослушиватель смены страниц, который добавляет больше проверок, чтобы гарантировать, что отклонения соответствуют порогу для переходов страниц. Оба этих слушателя позволяют вам легко ограничивать колебания по горизонтальной или вертикальной оси. Вы можете увидеть, как это используется в представлении для скольжения изображений . Я признаю, что люди здесь провели большую часть исследований - я просто собрал их в полезную библиотеку.

Эти последние несколько дней представляют мой первый опыт кодирования на Android; ожидайте гораздо большего .

Гаррет Уилсон
источник
Я хочу реализовать жест смахивания двумя пальцами. Пожалуйста, помогите мне!
Гаурав Арора
12

Это комбинированный ответ из двух ответов сверху, если кто-то хочет рабочую реализацию.

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() {}
}
Хай чжан
источник
Спасибо за действительно хорошую реализацию. Кроме того , я хотел бы предложить , чтобы проверить absDeltaY > minSwipeDelta, absVelocityY > minSwipeVelocity, absVelocityY < maxSwipeVelocityтолько в том случае , если minSwipeDelta ! = getScaledTouchSlop , minSwipeVelocity ! = getScaledMinimumFlingVelocity , maxSwipeVelocity ! = getScaledMaximumFlingVelocity , То есть только для проверки , если эти так называемые « по умолчанию» (я имею в виду getScaledTouchSlop, getScaledMinimumFlingVelocity, getScaledMaximumFlingVelocity) значения масштабируются или изменены в соответствии с вашими собственное желание.
Elia12345
Дело в том, что согласно исходному коду упомянутые значения «по умолчанию» уже проверены GestureDetector, и OnFling срабатывает только в том случае, если они подтверждены (кстати, запуск происходит только в случае ACTION_UP, а не ACTION_MOVEили ACTION_POINTER_UP, т. Е. Только как результат полностью реализованного жеста). (Я не проверял другие версии API, поэтому комментарии приветствуются).
Elia12345
11

Вы можете использовать библиотеку droidQuery для обработки бросков, кликов, длинных кликов и пользовательских событий. Реализация построена на моем предыдущем ответе ниже, но droidQuery предоставляет простой, простой синтаксис:

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

Оригинальный ответ

Этот ответ использует комбинацию компонентов из других ответов здесь. Он состоит из SwipeDetectorкласса, который имеет внутренний интерфейс для прослушивания событий. Я также предоставляю, RelativeLayoutчтобы показать, как переопределить метод a View, onTouchчтобы разрешить как события смахивания, так и другие обнаруженные события (такие как щелчки или длинные щелчки).

SwipeDetector

package self.philbrown;

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

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

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

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

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

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

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


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

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

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

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

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

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

Swipe Interceptor View

package self.philbrown;

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

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

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

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

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

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

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

    @Override
    public boolean onTouchEvent(MotionEvent e)
    {
        boolean swipe = false, touch = false;
        if (swiper != null)
            swipe = swiper.onTouch(this, e);
        touch = super.onTouchEvent(e);
        return swipe || touch;
    }
}
Фил
источник
1
Я попытался реализовать это в представлении, которое содержит интерактивные элементы. Когда смахивание начинается над активируемым щелчком элементом (например, представлением списка, в котором зарегистрирован прослушиватель onItemClick), тогда onTouchEvent никогда не вызывается. Таким образом, пользователь не может начать пролистывание элемента, реагирующего на нажатие, что мне не нравится, и я все еще пытаюсь выяснить, как обойти это, поскольку наши элементы, реагирующие на нажатие, занимают довольно мало места для просмотра, и мы по-прежнему хотим поддержку пролистывания для всего взгляда. Если смахивание не начинается поверх кликабельного элемента, то оно работает отлично.
Ло-Тан
@ Lo-Тан, это происходит потому , что ваш интерактивными элемент представляет собой вид ребенка, и, таким образом , на вершине из SwipeInterceptorView, так что его щелчок обрабатывается первым. Вы можете исправить это, внедрив свой собственный механизм onTouchListenerщелчков путем реализации , или в качестве обходного пути вы можете прослушивать длинные щелчки вместо щелчков (см. View.setOnLongClickListener).
Фил
Я на самом деле пытаюсь это сделать в тот самый момент. Или возможна отмена события клика, если они начинают перетаскивание :) Большое спасибо.
Ло-Тан
Одним из решений является подключение детектора прокрутки к каждому представлению в вашем приложении. Другой способ заключается в реализации onInterceptTouchEvent в вашем SwipeInterceptorView.
Эдвард Фальк
7

Я знаю, что уже слишком поздно, чтобы ответить, но все же я отправляю Swipe Detection for ListView, в которой рассказывается , как использовать Swipe Touch Listener в элементе ListView .

Refrence: Exterminator13 (один из ответов на этой странице)

Сделать один 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);
    }

}

Назовите это из своего класса активности следующим образом:

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

И не забудьте реализовать SwipeInterface, который даст вам два метода @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");
    }
Сагар Шах
источник
Я считаю, что MAX_OFF_PATH = 5 * vc.getScaledPagingTouchSlop()более удобно проводить пальцем по естественной дорожке, двигаясь по небольшой дуге.
Qix
4

Жесты - это тонкие движения, запускающие взаимодействие между сенсорным экраном и пользователем. Оно длится в течение времени между первым касанием экрана и моментом, когда последний палец покидает поверхность.

Android предоставляет нам класс GestureDetector, с помощью которого мы можем обнаружить обычные жесты, такие как касание вниз и вверх, пролистывание по вертикали и горизонтали (отбрасывание), долгое и короткое нажатие, двойное нажатие и т. Д. . Д. и прикрепить к ним слушателей.

Сделайте, чтобы наш класс Activity реализовал интерфейсы GestureDetector.OnDoubleTapListener (для обнаружения жестов двойным касанием) и GestureDetector.OnGestureListener и реализовал все абстрактные методы. Для получения дополнительной информации. Вы можете посетить https://developer.android.com/training/gestures/detector.html . учтивость

Для демонстрационного теста. GestureDetectorDemo

IntelliJ Amiya
источник
4

Если вам не нравится создавать отдельный класс или делать код сложным,
вы можете просто создать переменную GestureDetector внутри OnTouchListener и сделать ваш код более простым

namVyuVar может быть любым именем View, на которое нужно установить 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;
        }
    });
});
Суджай ООН
источник
3

Для всех: не забывайте о случае MotionEvent.ACTION_CANCEL:

это вызывает в 30% свайпов без ACTION_UP

и это равно ACTION_UP в этом случае

djdance
источник
2

Я установил более общий класс, взял класс Томаса и добавил интерфейс, который отправляет события вашей деятельности или фрагменту. он зарегистрирует прослушиватель в конструкторе, поэтому убедитесь, что вы реализуете интерфейс, иначе возникнет исключение ClassCastException. интерфейс возвращает одно из четырех последних значений int, определенных в классе, и возвращает представление, для которого он был активирован.

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

}
Гал Ром
источник