拖动时停止 OnLongClickListener 触发
Posted
技术标签:
【中文标题】拖动时停止 OnLongClickListener 触发【英文标题】:Stop OnLongClickListener from firing while dragging 【发布时间】:2011-09-10 11:21:34 【问题描述】:我有一个用户可以拖动的带有位图的自定义视图。
我想这样做,所以当他们长按其中一个时,我可以弹出一个上下文菜单,其中包含重置位置等选项。
在自定义视图中,我添加了我的 OnLongClickListener:
this.setOnLongClickListener(new View.OnLongClickListener()
@Override
public boolean onLongClick(View v)
// show context menu..
return true;
);
并覆盖 onTouchEvent 看起来像这样:
public boolean onTouchEvent(MotionEvent event)
handleDrag(event);
super.onTouchEvent(event);
return true;
handleDrag 函数查找被按下的对象,并处理更新它的位置。
我的问题是,当我开始拖动图像时,OnLongClickListener 也会触发。我不确定解决这个问题的最佳方法。
我尝试向 handleDrag 添加一个阈值,以便在用户触地但不尝试拖动时返回 false,但我发现仍然很难触发正确的处理程序。
谁能建议一种在拖动时跳过 OnLongClickListener 的方法?
【问题讨论】:
【参考方案1】:我想我已经通过调整阈值方法解决了这个问题。
首先,我将 onTouchEvent 更改为如下所示:
public boolean onTouchEvent(MotionEvent event)
mMultiTouchController.handleDrag(event);
return super.onTouchEvent(event);
它们现在都触发了,所以我将 OnLongClickListener 更改为以下内容:
this.setOnLongClickListener(new View.OnLongClickListener()
@Override
public boolean onLongClick(View v)
if (!mMultiTouchController.has_moved)
// Pop menu and done...
return false;
return true;
);
(mMultiTouchController 是包含我所有手势检测代码的类)。 这里的关键是在这个类中,我添加了 bool 'has_moved'。当我开始拖动时,我会计算增量:
float diffX = Math.abs(mCurrPtX - mPrevPt.getX());
float diffY = Math.abs(mCurrPtY - mPrevPt.getY());
if (diffX < threshold && diffY < threshold)
has_moved = false;
return;
现在,当 onLongClick 触发时,我知道是否要采取行动。
最后一块是设置:
setHapticFeedbackEnabled(false);
在我的视图中,这样用户就不会在每次 longClick 触发但不采取任何操作时都得到振动。我计划下一步手动进行振动。
到目前为止,这似乎还可以,希望对遇到与此类似情况的任何人有所帮助。
【讨论】:
【参考方案2】:我会停止使用 onLongClickListener 并只实现你自己的,这很容易做到。然后您就拥有了防止它们相互干扰所需的控制权。
以下代码实现了以下手势:拖动、点击、双击、长按和捏合。
static final short NONE = 0;
static final short DRAG = 1;
static final short ZOOM = 2;
static final short TAP = 3;
static final short DOUBLE_TAP = 4;
static final short POST_GESTURE = 5;
short mode = NONE;
static final float MIN_PINCH_DISTANCE = 30f;
static final float MIN_DRAG_DISTANCE = 5f;
static final float DOUBLE_TAP_MAX_DISTANCE = 30f;
static final long MAX_DOUBLE_TAP_MS = 1000;
static final long LONG_PRESS_THRESHOLD_MS = 2000;
public class Vector2d
public float x;
public float y;
public Vector2d()
x = 0f;
y = 0f;
public void set(float newX, float newY)
x = newX;
y = newY;
public Vector2d avgVector(Vector2d remote)
Vector2d mid = new Vector2d();
mid.set((remote.x + x)/2, (remote.y + y)/2);
return mid;
public float length()
return (float) Math.sqrt(x * x + y * y);
public float distance(Vector2d remote)
float deltaX = remote.x - x;
float deltaY = remote.y - y;
return (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
private Vector2d finger1 = new Vector2d();
private Vector2d finger2 = new Vector2d();
private Vector2d pinchStartDistance = new Vector2d();
private Vector2d pinchMidPoint;
private Vector2d fingerStartPoint = new Vector2d();
private long gestureStartTime;
private Marker selectedMarker;
@Override
public boolean onTouch(View v, MotionEvent event)
// Dump touch event to log
dumpEvent(event);
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK)
case MotionEvent.ACTION_DOWN:
finger1.set(event.getX(), event.getY());
if (mode == TAP)
if (finger1.distance(fingerStartPoint) < DOUBLE_TAP_MAX_DISTANCE)
mode = DOUBLE_TAP;
else
mode = NONE;
gestureStartTime = SystemClock.uptimeMillis();
else
gestureStartTime = SystemClock.uptimeMillis();
fingerStartPoint.set(event.getX(), event.getY());
break;
case MotionEvent.ACTION_POINTER_DOWN:
finger2.set(event.getX(1), event.getY(1));
pinchStartDistance.set(Math.abs(finger1.x - finger2.x), Math.abs(finger1.y - finger2.y));
Log.d(TAG, String.format("pinch start distance = %f, %f", pinchStartDistance.x, pinchStartDistance.y));
if (pinchStartDistance.length() > MIN_PINCH_DISTANCE)
if (pinchStartDistance.x < MIN_PINCH_DISTANCE)
pinchStartDistance.x = MIN_PINCH_DISTANCE;
if (pinchStartDistance.y < MIN_PINCH_DISTANCE)
pinchStartDistance.y = MIN_PINCH_DISTANCE;
pinchMidPoint = finger1.avgVector(finger2);
mode = ZOOM;
Log.d(TAG, "mode=ZOOM" );
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (mode == ZOOM)
Vector2d pinchEndDistance = new Vector2d();
pinchEndDistance.set(Math.abs(finger1.x - finger2.x), Math.abs(finger1.y - finger2.y));
if (pinchEndDistance.x < MIN_PINCH_DISTANCE)
pinchEndDistance.x = MIN_PINCH_DISTANCE;
if (pinchEndDistance.y < MIN_PINCH_DISTANCE)
pinchEndDistance.y = MIN_PINCH_DISTANCE;
Log.d(TAG, String.format("pinch end distance = %f, %f", pinchEndDistance.x, pinchEndDistance.y));
zoom(pinchMidPoint, pinchStartDistance.x/pinchEndDistance.x, pinchStartDistance.y/pinchEndDistance.y);
// Set mode to "POST_GESTURE" so that when the other finger lifts the handler won't think it was a
// tap or something.
mode = POST_GESTURE;
else if (mode == NONE)
// The finger wasn't moved enough for it to be considered a "drag", so it is either a tap
// or a "long press", depending on how long it was down.
if ((SystemClock.uptimeMillis() - gestureStartTime) < LONG_PRESS_THRESHOLD_MS)
Log.d(TAG, "mode=TAP");
mode = TAP;
selectedMarker = checkForMarker(finger1);
if (selectedMarker != null)
Log.d(TAG, "Selected marker, mode=NONE");
mode = NONE;
((Activity) parent).showDialog(ResultsActivity.DIALOG_MARKER_ID);
else
Log.d(TAG, "mode=LONG_PRESS");
addMarker(finger1);
requestRender();
else if (mode == DOUBLE_TAP && (SystemClock.uptimeMillis() - gestureStartTime) < MAX_DOUBLE_TAP_MS)
// The finger was again not moved enough for it to be considered a "drag", so it is
// a double-tap. Change the center point and zoom in.
Log.d(TAG, "mode=DOUBLE_TAP");
zoom(fingerStartPoint, 0.5f, 0.5f);
mode = NONE;
else
mode = NONE;
Log.d(TAG, "mode=NONE" );
break;
case MotionEvent.ACTION_MOVE:
if (mode == NONE || mode == TAP || mode == DOUBLE_TAP)
finger1.set(event.getX(), event.getY());
if (finger1.distance(fingerStartPoint) > MIN_DRAG_DISTANCE)
Log.d(TAG, "mode=DRAG" );
mode = DRAG;
scroll(fingerStartPoint.x - finger1.x, fingerStartPoint.y - finger1.y);
else if (mode == DRAG)
scroll(finger1.x - event.getX(), finger1.y - event.getY());
finger1.set(event.getX(), event.getY());
else if (mode == ZOOM)
for (int i=0; i<event.getPointerCount(); i++)
if (event.getPointerId(i) == 0)
finger1.set(event.getX(i), event.getY(i));
else if (event.getPointerId(i) == 1)
finger2.set(event.getX(i), event.getY(i));
else
Log.w(TAG, String.format("Unknown motion event pointer id: %d", event.getPointerId(i)));
break;
return true;
/** Show an event in the LogCat view, for debugging */
private void dumpEvent(MotionEvent event)
String names[] = "DOWN" , "UP" , "MOVE" , "CANCEL" , "OUTSIDE" ,
"POINTER_DOWN" , "POINTER_UP" , "7?" , "8?" , "9?" ;
StringBuilder sb = new StringBuilder();
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
sb.append("event ACTION_" ).append(names[actionCode]);
if (actionCode == MotionEvent.ACTION_POINTER_DOWN
|| actionCode == MotionEvent.ACTION_POINTER_UP)
sb.append("(pid " ).append(
action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
sb.append(")" );
sb.append("[" );
for (int i = 0; i < event.getPointerCount(); i++)
sb.append("#" ).append(i);
sb.append("(pid " ).append(event.getPointerId(i));
sb.append(")=" ).append((int) event.getX(i));
sb.append("," ).append((int) event.getY(i));
if (i + 1 < event.getPointerCount())
sb.append(";" );
sb.append("]" );
Log.d(TAG, sb.toString());
【讨论】:
【参考方案3】://此代码用于处理手势检测
final Handler handler = new Handler();
private Runnable mLongPressRunnable;
detector = new GestureDetector(this, new MyGestureDectector());
view.setOnTouchListener(new OnTouchListener()
@SuppressLint("ClickableViewAccessibility")
@SuppressWarnings("deprecation")
@Override
public boolean onTouch(View v, MotionEvent event)
detector.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_DOWN)
handler.postDelayed(mLongPressRunnable, 1000);
if ((event.getAction() == MotionEvent.ACTION_MOVE)
|| (event.getAction() == MotionEvent.ACTION_UP))
handler.removeCallbacks(mLongPressRunnable);
return true;
);
mLongPressRunnable = new Runnable()
public void run()
Toast.makeText(MainActivity.this, "long", Toast.LENGTH_SHORT)
.show();
;
class MyGestureDectector implements GestureDetector.OnDoubleTapListener,
OnGestureListener
//Implement all the methods
【讨论】:
注意:我发现至少在一台设备上,移动事件几乎在向下事件之后立即触发,即使我尝试完全不移动手指。因此,我建议不要在收到移动事件后立即取消长按(removeCallbacks
实际上是取消)。相反,对 ACCEPTED 答案中显示的“移动阈值”方法进行一些变化。这个答案的handler postDelayed/removeCallbacks
方法可以适应这样的移动阈值。以上是关于拖动时停止 OnLongClickListener 触发的主要内容,如果未能解决你的问题,请参考以下文章
我可以在运行时在 onClickListener 和 onLongClickListener 之间切换吗?