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

1002

Я хочу получить обнаружение gesture fling, работающее в моем приложении для Android.

У меня есть GridLayout, который содержит 9 ImageView s. Источник можно найти здесь: Розетка Grid Grid Layout.

Этот файл я беру от Romain Guy приложение Photostream и только немного адаптировался.

Для простой ситуации щелчка мне нужно только установить 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, я не знаю, как сообщить о действии, что a 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 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 (адаптировано из фотопотока).

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

Ничего не работало для меня до сих пор, и я надеялся на некоторые указатели.

  • 0
    Привет, у меня проблемы с моим GestureDetector, и я надеялся, что вы посмотрите на это. Я отправил вопрос с тем, какой код у меня есть: stackoverflow.com/questions/11742051/…
Теги:
listener
gesture-recognition

18 ответов

775
Лучший ответ

Благодаря Code Shogun, код которого я адаптировал к своей ситуации.

Пусть ваша деятельность реализует 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);
}

Танец "бросок" необязателен, но поощряется.

  • 107
    Спасибо за этот код! Это было очень полезно. Тем не менее, я столкнулся с одним очень очень неприятным уловом, пытаясь заставить жесты работать. В моем SimpleOnGestureListener я должен переопределить onDown для любого из моих жестов, чтобы зарегистрироваться. Это может просто вернуть true, но я должен быть определен. PS: я не знаю, моя ли это версия api или мое оборудование, но я использую 1.5 на HTC Droid Eris.
  • 7
    Я должен был также реализовать onDown. Очень мило с Cdsboy за то, что указал на это!
Показать ещё 21 комментарий
213

В одном из приведенных выше ответов говорится о работе с различной плотностью пикселей, но предлагается вручную вычислить параметры салфетки. Стоит отметить, что вы можете получить масштабированные, разумные значения из системы, используя класс 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)

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

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

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

onCreate просто добавляет его к самому нижнему расположению:

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

где id.lowestLayout - id.xxx для наименьшего представления в иерархии компоновки, а lowerLayout объявляется как 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
  • 5
    Это похоже на полезный служебный класс - но я думаю, что ваши четыре метода ... swipe () должны быть интерфейсами
Показать ещё 16 комментариев
86

Я слегка модифицировал и исправил решение от 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, и вы можете узнать, на чём был вызван просмотр события Swipe.

@Override
public void left2right(View v) {
    switch(v.getId()){
        case R.id.swipe_layout:
            // do your stuff here
        break;
    }       
}
  • 7
    Мне нравится эта версия. Другое дизайнерское решение, но выглядит отлично!
  • 0
    @ThomasFankhauser благодарит Томаса
Показать ещё 6 комментариев
58

Указанный выше код детектора жестов очень полезен! Однако вы можете сделать это агностическое значение плотности решения, используя следующие относительные значения (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);
  • 8
    +1 за то, что поднял это. Обратите внимание, что DensityMetrics.densityDpi был представлен в API 4. Для обратной совместимости с API 1 используйте взамен DensityMetrics.density. Это тогда изменяет вычисление, чтобы быть просто SWIPE_MIN_DISTANCE * dm.density.
  • 0
    Где вы взяли номер 160.0f?
Показать ещё 5 комментариев
31

Моя версия решения, предложенная Thomas Fankhauser и Марек Себера (не обрабатывает вертикальные удары):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

10

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

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

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

  • 0
    Я хочу реализовать жест смахивания двумя пальцами. Пожалуйста, помогите мне!
8

Вы можете использовать библиотеку 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, чтобы показать, как переопределить метод 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

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 никогда не вызывается. Таким образом, пользователь не может начать пролистывание элемента, реагирующего на нажатие, что мне не нравится, и я все еще пытаюсь выяснить, как обойти это, поскольку наши элементы, реагирующие на нажатие, занимают немного места для просмотра, и нам все еще нужна поддержка пролистывания для всего взгляда. Если смахивание не начинается с кликабельного элемента, то оно работает отлично.
  • 0
    @ Lo-Tan, это происходит потому, что ваш кликабельный элемент является дочерним представлением и, таким образом, находится над SwipeInterceptorView , поэтому его щелчок обрабатывается первым. Вы можете исправить это, внедрив свой собственный механизм onTouchListener помощью onTouchListener , или в качестве onTouchListener вы можете прослушивать длинные щелчки вместо щелчков (см. View.setOnLongClickListener ).
Показать ещё 2 комментария
5

Я знаю, что слишком поздно ответить, но все же я отправляю Swipe Detection для 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");
    }
  • 0
    Я считаю, что MAX_OFF_PATH = 5 * vc.getScaledPagingTouchSlop() удобнее для проведения пальца, естественно, движущегося по небольшой дуге.
1

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

namVyuVar может быть любым именем представления, на котором вам нужно установить 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;
        }
    });
});
1

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

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

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

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

1

Всем: не забывайте о случае MotionEvent.ACTION_CANCEL:

он вызывает 30% проверок без ACTION_UP

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

0

Я установил более общий класс, я взял класс Tomas и добавил интерфейс, который отправляет события в вашу активность или фрагмент. он будет регистрировать слушателя на конструкторе, поэтому убедитесь, что вы реализуете интерфейс, или 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);
    }

}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню