Как различать перемещение и щелчок в onTouchEvent ()?

В моем приложении мне нужно обрабатывать события перемещения и клика.

Щелчок представляет собой последовательность из одного действия ACTION_DOWN, несколько действий ACTION_MOVE и одно действие ACTION_UP. Теоретически, если вы получаете событие ACTION_DOWN, а затем событие ACTION_UP – это означает, что пользователь только что нажал кнопку «Просмотр».

Но на практике эта последовательность не работает на некоторых устройствах. На моем Samsung Galaxy Gio я получаю такие последовательности, просто щелкнув мой View: ACTION_DOWN, несколько раз ACTION_MOVE, затем ACTION_UP. Т.е. я получаю некоторые непредсказуемые стрельбы OnTouchEvent с кодом действия ACTION_MOVE. Я никогда (или почти никогда) не получаю последовательность ACTION_DOWN -> ACTION_UP.

Я также не могу использовать OnClickListener, потому что он не дает позиции щелчка. Итак, как я могу определить событие click и отличать его от перемещения?

Вот еще одно очень простое решение и не требует, чтобы вы беспокоились о перемещении пальца. Если вы используете клик как просто перемещенное расстояние, то как вы можете отличить клик и длинный щелчок.

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

setOnTouchListener(new OnTouchListener() { private static final int MAX_CLICK_DURATION = 200; private long startClickTime; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { startClickTime = Calendar.getInstance().getTimeInMillis(); break; } case MotionEvent.ACTION_UP: { long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime; if(clickDuration < MAX_CLICK_DURATION) { //click event has occurred } } } return true; } }); 

Я получил наилучшие результаты, принимая во внимание:

  1. Прежде всего, расстояние, перемещаемое между ACTION_DOWN и ACTION_UP . Я хотел указать максимальное допустимое расстояние в пикселях с плотностями, а не в пикселях, чтобы лучше поддерживать разные экраны. Например, 15 DP .
  2. Во-вторых, продолжительность между событиями. Одна секунда показалась хорошим максимумом. (Некоторые люди «нажимают» довольно «торочно», то есть медленно, я все еще хочу это признать.)

Пример:

 /** * Max allowed duration for a "click", in milliseconds. */ private static final int MAX_CLICK_DURATION = 1000; /** * Max allowed distance to move during a "click", in DP. */ private static final int MAX_CLICK_DISTANCE = 15; private long pressStartTime; private float pressedX; private float pressedY; @Override public boolean onTouchEvent(MotionEvent e) { switch (e.getAction()) { case MotionEvent.ACTION_DOWN: { pressStartTime = System.currentTimeMillis(); pressedX = e.getX(); pressedY = e.getY(); break; } case MotionEvent.ACTION_UP: { long pressDuration = System.currentTimeMillis() - pressStartTime; if (pressDuration < MAX_CLICK_DURATION && distance(pressedX, pressedY, e.getX(), e.getY()) < MAX_CLICK_DISTANCE) { // Click event has occurred } } } } private static float distance(float x1, float y1, float x2, float y2) { float dx = x1 - x2; float dy = y1 - y2; float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy); return pxToDp(distanceInPx); } private static float pxToDp(float px) { return px / getResources().getDisplayMetrics().density; } 

Идея здесь такая же, как и в решении Gem , с этими различиями:

  • Это вычисляет фактическое евклидово расстояние между двумя точками.
  • Это использует dp вместо px.

Обновление (2015): также проверьте настроенную версию Габриэля .

Взяв Jonik, я построил немного более тонкую версию, которая не регистрируется как щелчок, если вы двигаете пальцем, а затем возвращаетесь на место, прежде чем отпускать:

Итак, вот мое решение:

 /** * Max allowed duration for a "click", in milliseconds. */ private static final int MAX_CLICK_DURATION = 1000; /** * Max allowed distance to move during a "click", in DP. */ private static final int MAX_CLICK_DISTANCE = 15; private long pressStartTime; private float pressedX; private float pressedY; private boolean stayedWithinClickDistance; @Override public boolean onTouchEvent(MotionEvent e) { switch (e.getAction()) { case MotionEvent.ACTION_DOWN: { pressStartTime = System.currentTimeMillis(); pressedX = e.getX(); pressedY = e.getY(); stayedWithinClickDistance = true; break; } case MotionEvent.ACTION_MOVE: { if (stayedWithinClickDistance && distance(pressedX, pressedY, e.getX(), e.getY()) > MAX_CLICK_DISTANCE) { stayedWithinClickDistance = false; } break; } case MotionEvent.ACTION_UP: { long pressDuration = System.currentTimeMillis() - pressStartTime; if (pressDuration < MAX_CLICK_DURATION && stayedWithinClickDistance) { // Click event has occurred } } } } private static float distance(float x1, float y1, float x2, float y2) { float dx = x1 - x2; float dy = y1 - y2; float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy); return pxToDp(distanceInPx); } private static float pxToDp(float px) { return px / getResources().getDisplayMetrics().density; } 

Используйте детектор, он работает, и он не будет подниматься при перетаскивании

поле:

 private GestureDetector mTapDetector; 

Инициализировать:

 mTapDetector = new GestureDetector(context,new GestureTap()); 

Внутренний class:

 class GestureTap extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDoubleTap(MotionEvent e) { return true; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { // TODO: handle tap here return true; } } 

onTouch:

 @Override public boolean onTouch(View v, MotionEvent event) { mTapDetector.onTouchEvent(event); return true; } 

Наслаждаться 🙂

Чтобы получить наиболее оптимизированное распознавание события click, нам нужно рассмотреть 2 вещи:

  1. Разница во времени между ACTION_DOWN и ACTION_UP.
  2. Разница между x, y, когда пользователь прикасается и разблокирует палец.

На самом деле я совмещаю логику, данную Стимсони и Неетираном

Итак, вот мое решение:

  view.setOnTouchListener(new OnTouchListener() { private final int MAX_CLICK_DURATION = 400; private final int MAX_CLICK_DISTANCE = 5; private long startClickTime; private float x1; private float y1; private float x2; private float y2; private float dx; private float dy; @Override public boolean onTouch(View view, MotionEvent event) { // TODO Auto-generated method stub switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { startClickTime = Calendar.getInstance().getTimeInMillis(); x1 = event.getX(); y1 = event.getY(); break; } case MotionEvent.ACTION_UP: { long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime; x2 = event.getX(); y2 = event.getY(); dx = x2-x1; dy = y2-y1; if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) Log.v("","On Item Clicked:: "); } } return false; } }); 

Ниже код решит вашу проблему.

     @Override
         public boolean onTouchEvent (событие MotionEvent) {
             switch (event.getAction ()) {
                 случай (MotionEvent.ACTION_DOWN):
                     x1 = event.getX ();
                     y1 = event.getY ();
                     ломать;
                 case (MotionEvent.ACTION_UP): {
                     x2 = event.getX ();
                     y2 = event.getY ();
                     dx = x2-x1;
                     dy = y2-y1;

                 if (Math.abs (dx)> Math.abs (dy)) 
                 {
                     если (dx> 0) двигаться (1);  //правильно
                     else if (dx == 0) move (5);  // нажмите
                     else move (2);  //оставил
                 } 
                 еще 
                 {
                     если (dy> 0) двигаться (3);  // вниз
                     else if (dy == 0) move (5);  // нажмите
                     else move (4);  // вверх
                 }
                 }
             }
             return true;
         }

Очень сложно, чтобы ACTION_DOWN произошел без ACTION_MOVE. Малейшее подергивание пальца на экране в другом месте, чем при первом касании, вызовет событие MOVE. Кроме того, я считаю, что изменение давления пальца также вызовет событие MOVE. Я бы использовал оператор if в методе Action_Move, чтобы попытаться определить расстояние, которое произошло от первоначального движения DOWN. если перемещение произошло вне определенного радиуса набора, ваше действие MOVE произойдет. Это, вероятно, не лучший, ресурсоэффективный способ сделать то, что вы пытаетесь, но он должен работать.

Если вы хотите реагировать только на клик, используйте:

 if (event.getAction() == MotionEvent.ACTION_UP) { } 

Добавляя к приведенным выше ответам, если вы хотите реализовать как действия onClick, так и Drag, тогда мой код ниже может вы, ребята. Принимая некоторую помощь от @Stimsoni:

  // assumed all the variables are declared globally; public boolean onTouch(View view, MotionEvent event) { int MAX_CLICK_DURATION = 400; int MAX_CLICK_DISTANCE = 5; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { long clickDuration1 = Calendar.getInstance().getTimeInMillis() - startClickTime; startClickTime = Calendar.getInstance().getTimeInMillis(); x1 = event.getX(); y1 = event.getY(); break; } case MotionEvent.ACTION_UP: { long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime; x2 = event.getX(); y2 = event.getY(); dx = x2-x1; dy = y2-y1; if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) { Toast.makeText(getApplicationContext(), "item clicked", Toast.LENGTH_SHORT).show(); Log.d("clicked", "On Item Clicked:: "); // imageClickAction((ImageView) view,rl); } } case MotionEvent.ACTION_MOVE: long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime; x2 = event.getX(); y2 = event.getY(); dx = x2-x1; dy = y2-y1; if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) { //Toast.makeText(getApplicationContext(), "item clicked", Toast.LENGTH_SHORT).show(); // Log.d("clicked", "On Item Clicked:: "); // imageClickAction((ImageView) view,rl); } else { ClipData clipData = ClipData.newPlainText("", ""); View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view); //Toast.makeText(getApplicationContext(), "item dragged", Toast.LENGTH_SHORT).show(); view.startDrag(clipData, shadowBuilder, view, 0); } break; } return false; } 

Используя ответ Gil SH, я улучшил его, реализовав onSingleTapUp() а не onSingleTapConfirmed() . Это намного быстрее и не будет нажимать на представление, если оно перетаскивается / перемещается.

GestureTap:

 public class GestureTap extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { button.performClick(); return true; } } 

Используйте его так:

 final GestureDetector gestureDetector = new GestureDetector(getApplicationContext(), new GestureTap()); button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { gestureDetector.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: return true; case MotionEvent.ACTION_UP: return true; case MotionEvent.ACTION_MOVE: return true; } return false; } }); 
Давайте будем гением компьютера.