How to distinguish between move and click in onTouchEvent()?
在我的应用程序中,我需要处理移动和单击事件。
点击是一个ACTION_DOWN动作,几个ACTION_MOVE动作和一个ACTION_UP动作的序列。 从理论上讲,如果您收到一个ACTION_DOWN事件,然后再收到一个ACTION_UP事件-这意味着用户刚刚单击了您的视图。
但实际上,此序列在某些设备上不起作用。 在我的Samsung Galaxy Gio上,只要单击我的视图,我就会得到这样的序列:ACTION_DOWN,几次ACTION_MOVE,然后是ACTION_UP。 即 我收到一些带有ACTION_MOVE操作代码的意外OnTouchEvent触发。 我从未(或几乎从未)获得序列ACTION_DOWN-> ACTION_UP。
我也不能使用OnClickListener,因为它没有给出点击的位置。 因此,如何检测点击事件并将其与移动区别?
这是另一种非常简单的解决方案,不需要您担心手指被移动。如果您仅将点击作为移动距离的基础,那么如何区分点击和长时间点击。
您可以在其中加入更多的技巧,并包括移动的距离,但是我还没有遇到一个实例,即用户可以在200毫秒内移动的距离应该构成一次移动而不是单击。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | /** * 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):还请查看Gabriel对此的微调版本。
在Jonik的带领下,我构建了一个稍微调整的版本,如果您移动手指然后放回原位,则不会点击一下:
所以这是我的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | /** * 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; } |
使用检测器,它可以正常工作,并且在拖动时不会升高
领域:
1 | private GestureDetector mTapDetector; |
初始化:
1 | mTapDetector = new GestureDetector(context,new GestureTap()); |
内部舱位:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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:
1 2 3 4 5 | @Override public boolean onTouch(View v, MotionEvent event) { mTapDetector.onTouchEvent(event); return true; } |
请享用 :)
为了获得对点击事件的最优化识别,我们必须考虑两件事:
实际上,我结合了Stimsoni和Neethirajan给出的逻辑
所以这是我的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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; } }); |
使用Gil SH答案,我通过实现
手势点击:
1 2 3 4 5 6 7 | public class GestureTap extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { button.performClick(); return true; } } |
像这样使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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; } }); |
下面的代码将解决您的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | @Override public boolean onTouchEvent(MotionEvent event) { switch(event.getAction()) { case(MotionEvent.ACTION_DOWN): x1 = event.getX(); y1 = event.getY(); break; case(MotionEvent.ACTION_UP): { x2 = event.getX(); y2 = event.getY(); dx = x2-x1; dy = y2-y1; if(Math.abs(dx) > Math.abs(dy)) { if(dx>0) move(1); //right else if (dx == 0) move(5); //click else move(2); //left } else { if(dy>0) move(3); // down else if (dy == 0) move(5); //click else move(4); //up } } } return true; } |
没有ACTION_MOVE发生,很难进行ACTION_DOWN。在屏幕上与第一次触摸不同的位置上,手指在屏幕上的最小抽动将触发MOVE事件。另外,我相信手指压力的变化也将触发MOVE事件。我将在Action_Move方法中使用if语句来尝试确定移动与原始DOWN运动之间的距离。如果移动发生在某个设定半径之外,则将执行"移动"动作。这可能不是最好的,资源高效的方式来完成您的尝试,但它应该可以工作。
除了上述答案外,如果您想同时执行onClick和Drag动作,那么我下面的代码可以。从@Stimsoni获得一些帮助:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | // 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; } |
如果您只想对点击做出反应,请使用:
1 2 3 | if (event.getAction() == MotionEvent.ACTION_UP) { } |