Розпізнавання жестів на макеті сітки


1105

Я хочу, щоб flingмоє детектування жестів працювало в моєму додатку Android.

У мене є GridLayoutте, що містить 9 ImageViewс. Джерело можна знайти тут: Макет сітки Ромена Гая .

Цей файл, який я беру, є із програми Photostream від Romain Guy і лише трохи адаптований.

Для простої ситуації клацання мені потрібно встановити лише те, що 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

Завдяки коду 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);
}

Пост "кинути" танець не є обов'язковим, але заохочується.


109
Дякую за цей код! Це було дуже корисно. Однак я зіткнувся з одним дуже розчаровуючим уловом, намагаючись працювати жестами. У моєму SimpleOnGestureListener я повинен перекрити onDown, щоб зареєструвати будь-який із своїх жестів. Він може просто повернути правду, але я повинен бути визначений. PS: Я не знаю, чи це моя редакція api чи моє обладнання, але я використовую 1,5 на HTC Droid Eris.
Cdsboy

Я спробував ваш код, і не має значення, якщо я проведіть пальцем або клацніть (мишкою, тому що я працюю в емуляторі), я завжди отримую Toast, визначений у методі onClick, тому емулятор виявляє лише кліки, без пальців. Чому так?
ломза

Я спробував цей код, і він не спрацював. все ще не вдалося прокручуватись, коли я застосував слухач onClick до одного з дитячих переглядів у моїй галереї
Jonathan

Iomza: ти намагався ввести заяви про перерву та переглядати свій код?
ІгорГанапольський

Кудо для використання внутрішнього класу! Дуже чистий підхід.
ІгорГанапольський

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)

Я помітив, що використання цих значень призводить до того, що "відчуття" fling є більш послідовним між додатком та рештою системи.


11
Я використовую swipeMinDistance = vc.getScaledPagingTouchSlop()і swipeMaxOffPath = vc.getScaledTouchSlop().
Thomas Ahle

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 для перегляду, найнижчого в ієрархії макета, а найнижчий макет оголошується як відносний макет

А далі є фактичний клас детектування пальцем активності:

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 () повинні бути інтерфейси
Хтось десь

2
цих повернень не повинно бути там (рядок "ми не споживаємо події"), чи не так? Це вимикає функцію вертикальної прокрутки.
Марек Себера

5
конкретно, метод onTouch (). по-перше, якщо дельта X недостатньо велика, вона повертається, не перевіряючи дельту Y. Результат полягає в тому, що ніколи не виявляє пальці ліво-праворуч. по-друге, він також не повинен повертати справжнє значення, якщо воно не проходить, не провівши пальцем. по-третє, він не повинен повертати істину на дії вниз. це запобігає роботі будь-якого іншого слухача, як onClick.
Джефрі Блатман

1
@Piotr - це не проблема, якщо об'єкт, що містить посилання, має той самий обсяг, що і сама діяльність. проблема виникає, коли ви зберігаєте посилання на активність у місці, яке має більший обсяг, ніж діяльність ... наприклад, у статичного члена.
Джефрі Блатман

94

Я трохи змінив і відремонтував рішення від Томаса Фанхаузера

Вся система складається з двох файлів, 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;
    }       
}

Я трохи змінив його ще раз, дивіться той v.performClick();, який використовується для того, щоб він не споживав події для OnClickListener, якщо встановлено той самий вигляд
Marek Sebera

Привіт, я зовсім початківець, тому це питання може бути справді очевидним чи тривіальним, але будь ласка, дайте відповідь. Частина, де ви написали, використовується як: Swipe ActivitySwipeDetector = новий ActivitySwipeDetector (це); Це твердження буде частиною MainActivity, правильно? Тоді "це" буде діяльністю MainActivity. Тоді як конструктор бере екземпляр SwipeInterface. Будь ласка, допоможіть мені тут. Дуже дякую.
Chocolava

@Chocolava створити нове запитання, коментар - це не вдале місце для запитання, як це.
Марек Себера

@MarekSebera це не працює із ScrollView & ListView? як з ними поводитися?
Duc Tran

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

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

Я все це шукав. НІ в прикладі онфлінгу () в Інтернеті немає цього, що призведе до поганого UX. Дякую!
Сенді

160.0f походить від 160 DPI, яка є стандартною щільністю, на якій заснована DP (незалежна від щільності пікселів). загальнодоступний статичний кінцевий int DENSITY_MEDIUM Додано в рівні 4 API Стандартний квантований 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;
    }

}

хто-небудь, будь ласка, скажіть мені, як називати клас. ActivitySwipeDetector проведіть пальцем = новий ActivitySwipeDetector (це); Очевидно є помилка, як немає такого конструктора. Чи слід навести поворот ActivitySwipeDetector = новий ActivitySwipeDetector (це, нульовий);
абдфахім

@AbdullahFahim ActivitySwipeDetector (це, YourActivity.this);
Антон Кашпор

25

Це питання є старим, і в липні 2011 року Google випустив Пакет сумісності, редакція 3), який включає в себеViewPager що працює з Android 1.6 вгору. У GestureListenerвідповідь відповідав на це питання не відчуваю себе дуже елегантно на Android. Якщо ви шукаєте код, який використовується для перемикання між фотографіями в галереї Android або перемикання поглядів у новому додатку Play Market, то це безумовно ViewPager.

Ось декілька посилань для отримання додаткової інформації:


Одна з проблем у ViewPager полягає в тому, що ви не маєте контролю параметрів відстані та швидкості для жеста, що рухається.
almalkawi

ViewPager не використовується в галереї.
Ентоні

18

Є вбудований інтерфейс, який ви можете використовувати безпосередньо для всіх жестів:
Ось пояснення для користувача базового рівня: введіть тут опис зображення Є два імпорти, будьте обережні, обираючи, що обидва є різними введіть тут опис зображення введіть тут опис зображення


1
А які наступні кроки? Як встановити того слухача на певний вигляд? А що, якщо цей погляд є частиною фрагмента?
Стен

16

У Інтернеті (і на цій сторінці) є пропозиція використовувати ViewConfiguration. getScaledTouchSlop () має значення для масштабування пристрою SWIPE_MIN_DISTANCE.

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

Завдяки API рівня 8 (Android 2.2, Froyo) ви отримали getScaledPagingTouchSlop()призначене для перегортання сторінки. На моєму пристрої він повертає 24 (пікселі). Тож якщо ви перебуваєте на рівні API <8, я думаю, що "2 * getScaledTouchSlop()" має бути "стандартним" порогом пальця. Але користувачі моєї програми з маленьких екранів сказали мені, що це занадто мало ... Як і в моєму додатку, ви можете прокручувати вертикально та змінювати сторінку по горизонталі. За допомогою запропонованого значення вони іноді змінюють сторінку замість прокрутки.


13

Також як незначне посилення.

Основна причина блоку спробу / лову полягає в тому, що e1 може бути нульовим для початкового руху. окрім спроби / лову, додайте тест на 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показати, як змінити метод View's, 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);
    }
}

Проведіть пальцем Перегляд перехоплювача

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

Я знаю, що це занадто пізно, щоб відповісти, але все ж я публікую Швидке визначення для ListView те, як користуватися перекладачем сенсорного дотику в елементі ListView .

Рефренс: Винищувач13 (одна з відповідей на цій сторінці)

Зробіть один 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");
    }

Я вважаю, що a MAX_OFF_PATH = 5 * vc.getScaledPagingTouchSlop()зручніше для пальця пальцем, природно, подорожуючи невеликою дугою.
qix

4

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

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

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

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


4

Якщо вам не подобається створювати окремий клас або складати код складним,
ви можете просто створити змінну GestureDetector всередині OnTouchListener і полегшити свій код

namVyuVar може бути будь-яким ім'ям Перегляду, на який потрібно встановити список

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

Для всіх: не забувайте про case MotionEvent.ACTION_CANCEL:

він дзвонить 30% пальцями без ACTION_UP

і дорівнює в цьому випадку ACTION_UP


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

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.