Обнаружение жестов на макете сетки - PullRequest
1060 голосов
/ 02 июня 2009

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

У меня есть GridLayout, который содержит 9 ImageView с. Источник может быть найден здесь: Схема решетки Romain Guys's .

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

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

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

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

Ответы [ 18 ]

798 голосов
/ 02 июня 2009

Благодаря 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);
}

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

208 голосов
/ 20 апреля 2011

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

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

144 голосов
/ 21 апреля 2011

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

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

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

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

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

public class ActivitySwipeDetector implements View.OnTouchListener {

static final String logTag = "ActivitySwipeDetector";
private Activity activity;
static final int MIN_DISTANCE = 100;
private float downX, downY, upX, upY;

public ActivitySwipeDetector(Activity activity){
    this.activity = activity;
}

public void onRightSwipe(){
    Log.i(logTag, "RightToLeftSwipe!");
    activity.doSomething();
}

public void onLeftSwipe(){
    Log.i(logTag, "LeftToRightSwipe!");
    activity.doSomething();
}

public void onDownSwipe(){
    Log.i(logTag, "onTopToBottomSwipe!");
    activity.doSomething();
}

public void onUpSwipe(){
    Log.i(logTag, "onBottomToTopSwipe!");
    activity.doSomething();
}

public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            upX = event.getX();
            upY = event.getY();

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

       // swipe horizontal?
        if(Math.abs(deltaX) > Math.abs(deltaY))
        {
            if(Math.abs(deltaX) > MIN_DISTANCE){
                // left or right
                if(deltaX > 0) { this.onRightSwipe(); return true; }
                if(deltaX < 0) { this.onLeftSwipe(); return true; }
            }
            else {
                    Log.i(logTag, "Horizontal Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                    return false; // We don't consume the event
            }
        }
        // swipe vertical?
        else 
        {
            if(Math.abs(deltaY) > MIN_DISTANCE){
                // top or down
                if(deltaY < 0) { this.onDownSwipe(); return true; }
                if(deltaY > 0) { this.onUpSwipe(); return true; }
            }
            else {
                    Log.i(logTag, "Vertical Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                    return false; // We don't consume the event
            }
        }

            return true;
        }
    }
    return false;
}

}

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

93 голосов
/ 10 января 2012

Я слегка модифицировал и отремонтировал решение из Томас Фанкхаузер

Вся система состоит из двух файлов: SwipeInterface и ActivitySwipeDetector


SwipeInterface.java

import android.view.View;

public interface SwipeInterface {

    public void bottom2top(View v);

    public void left2right(View v);

    public void right2left(View v);

    public void top2bottom(View v);

}

детектор

import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class ActivitySwipeDetector implements View.OnTouchListener {

    static final String logTag = "ActivitySwipeDetector";
    private SwipeInterface activity;
    static final int MIN_DISTANCE = 100;
    private float downX, downY, upX, upY;

    public ActivitySwipeDetector(SwipeInterface activity){
        this.activity = activity;
    }

    public void onRightToLeftSwipe(View v){
        Log.i(logTag, "RightToLeftSwipe!");
        activity.right2left(v);
    }

    public void onLeftToRightSwipe(View v){
        Log.i(logTag, "LeftToRightSwipe!");
        activity.left2right(v);
    }

    public void onTopToBottomSwipe(View v){
        Log.i(logTag, "onTopToBottomSwipe!");
        activity.top2bottom(v);
    }

    public void onBottomToTopSwipe(View v){
        Log.i(logTag, "onBottomToTopSwipe!");
        activity.bottom2top(v);
    }

    public boolean onTouch(View v, MotionEvent event) {
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            upX = event.getX();
            upY = event.getY();

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

            // swipe horizontal?
            if(Math.abs(deltaX) > MIN_DISTANCE){
                // left or right
                if(deltaX < 0) { this.onLeftToRightSwipe(v); return true; }
                if(deltaX > 0) { this.onRightToLeftSwipe(v); return true; }
            }
            else {
                Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
            }

            // swipe vertical?
            if(Math.abs(deltaY) > MIN_DISTANCE){
                // top or down
                if(deltaY < 0) { this.onTopToBottomSwipe(v); return true; }
                if(deltaY > 0) { this.onBottomToTopSwipe(v); return true; }
            }
            else {
                Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                v.performClick();
            }
        }
        }
        return false;
    }

}

используется следующим образом:

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

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

@Override
public void left2right(View v) {
    switch(v.getId()){
        case R.id.swipe_layout:
            // do your stuff here
        break;
    }       
}
65 голосов
/ 18 февраля 2011

Приведенный выше код детектора жестов очень полезен! Однако вы можете захотеть сделать эту плотность раствора независимой, используя следующие относительные значения (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);
35 голосов
/ 18 июля 2012

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

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

}
25 голосов
/ 23 апреля 2012

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

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

18 голосов
/ 05 апреля 2013

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

15 голосов
/ 10 февраля 2012

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

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

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

13 голосов
/ 13 апреля 2011

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

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

if (e1 == null || e2 == null) return false;
try {
...
} catch (Exception e) {}
return false;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...