GestureDetector
手勢檢測,用于輔助檢測使用者的單擊、滑動、長按、輕按兩下等行為。參考如下。
GestureDetector gestureDetector=new GestureDetector(this, new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onDown(MotionEvent e) {
//點了一下螢幕,ACTION_DOWN觸發
return false;
}
@Override
public void onShowPress(MotionEvent e) {
//點選螢幕中,沒有松開和移動,ACTION_DOWN觸發
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
//單擊事件,輕輕點選螢幕後松開,ACTION_DOWN觸發和ACTION_UP觸發
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//拖動事件,點選螢幕後拖動,ACTION_DOWN觸發和多個ACTION_MOVE觸發
return false;
}
@Override
public void onLongPress(MotionEvent e) {
//長按事件,點選螢幕中,沒有松開和移動,ACTION_DOWN觸發
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//滑動事件,點選螢幕快速滑動松開,ACTION_DOWN觸發和多個ACTION_MOVE、一個ACTION_UP觸發
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
//輕按兩下事件,快速點選了兩下螢幕
return super.onDoubleTap(e);
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
//輕按兩下事件中,ACTION_DOWN、ACTION_UP和ACTION_MOVE都會觸發這個回調
return super.onDoubleTapEvent(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
//嚴格的單擊事件,不可能是輕按兩下中的一次
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onContextClick(MotionEvent e) {
//發生上下文單擊時候都會觸發
return super.onContextClick(e);
}
});
return super.onTouchEvent(event);
}
解決長按螢幕無法拖動問題
gestureDetector.setIsLongpressEnabled(false);
我們可以看看GestureDetector 的源碼,裡面其實是分開了三個接口OnGestureListener,OnDoubleTapListener,OnContextClickListener
OnGestureListener
public interface OnGestureListener {
boolean onDown(MotionEvent e);
void onShowPress(MotionEvent e);
boolean onSingleTapUp(MotionEvent e);
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
void onLongPress(MotionEvent e);
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
}
OnDoubleTapListener
public interface OnDoubleTapListener {
boolean onSingleTapConfirmed(MotionEvent e);
boolean onDoubleTap(MotionEvent e);
boolean onDoubleTapEvent(MotionEvent e);
}
OnContextClickListener
public interface OnContextClickListener {
boolean onContextClick(MotionEvent e);
}
SimpleOnGestureListener這是實作了三個接口,它是一個内部類,這樣你可以選擇自己需要的部分進行重寫,系統在設計方面還是比較詳細的
public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
OnContextClickListener {
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
public void onLongPress(MotionEvent e) {
}
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
return false;
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return false;
}
public void onShowPress(MotionEvent e) {
}
public boolean onDown(MotionEvent e) {
return false;
}
public boolean onDoubleTap(MotionEvent e) {
return false;
}
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
public boolean onContextClick(MotionEvent e) {
return false;
}
}
其實這些功能我們也可以自己在onTounchEvent中實作,可以看看源碼也是這樣做的
public boolean onTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
}
final int action = ev.getAction();
if (mCurrentMotionEvent != null) {
mCurrentMotionEvent.recycle();
}
mCurrentMotionEvent = MotionEvent.obtain(ev);
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final boolean pointerUp =
(action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
final boolean isGeneratedGesture =
(ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
// Determine focal point
float sumX = 0, sumY = 0;
final int count = ev.getPointerCount();
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
sumX += ev.getX(i);
sumY += ev.getY(i);
}
final int div = pointerUp ? count - 1 : count;
final float focusX = sumX / div;
final float focusY = sumY / div;
boolean handled = false;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
// Cancel long press and taps
cancelTaps();
break;
case MotionEvent.ACTION_POINTER_UP:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
// Check the dot product of current velocities.
// If the pointer that left was opposing another velocity vector, clear.
mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final int upIndex = ev.getActionIndex();
final int id1 = ev.getPointerId(upIndex);
final float x1 = mVelocityTracker.getXVelocity(id1);
final float y1 = mVelocityTracker.getYVelocity(id1);
for (int i = 0; i < count; i++) {
if (i == upIndex) continue;
final int id2 = ev.getPointerId(i);
final float x = x1 * mVelocityTracker.getXVelocity(id2);
final float y = y1 * mVelocityTracker.getYVelocity(id2);
final float dot = x + y;
if (dot < 0) {
mVelocityTracker.clear();
break;
}
}
break;
case MotionEvent.ACTION_DOWN:
if (mDoubleTapListener != null) {
boolean hadTapMessage = mHandler.hasMessages(TAP);
if (hadTapMessage) mHandler.removeMessages(TAP);
if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null)
&& hadTapMessage
&& isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
// This is a second tap
mIsDoubleTapping = true;
recordGestureClassification(
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
// Give a callback with the first tap of the double-tap
handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
// Give a callback with down event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else {
// This is a first tap
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
}
}
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = false;
mDeferConfirmSingleTap = false;
mHasRecordedClassification = false;
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendMessageAtTime(
mHandler.obtainMessage(
LONG_PRESS,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
0 ),
mCurrentDownEvent.getDownTime()
+ ViewConfiguration.getLongPressTimeout());
}
mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;
case MotionEvent.ACTION_MOVE:
if (mInLongPress || mInContextClick) {
break;
}
final int motionClassification = ev.getClassification();
final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS);
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) {
// Give the move events of the double-tap
recordGestureClassification(
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) {
final int deltaX = (int) (focusX - mDownFocusX);
final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
final boolean shouldInhibitDefaultAction =
hasPendingLongPress && ambiguousGesture;
if (shouldInhibitDefaultAction) {
// Inhibit default long press
if (distance > slopSquare) {
// The default action here is to remove long press. But if the touch
// slop below gets increased, and we never exceed the modified touch
// slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing*
// will happen in response to user input. To prevent this,
// reschedule long press with a modified timeout.
mHandler.removeMessages(LONG_PRESS);
final long longPressTimeout = ViewConfiguration.getLongPressTimeout();
mHandler.sendMessageAtTime(
mHandler.obtainMessage(
LONG_PRESS,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
0 ),
ev.getDownTime()
+ (long) (longPressTimeout * mAmbiguousGestureMultiplier));
}
// Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
// until the gesture is resolved.
// However, for safety, simply increase the touch slop in case the
// classification is erroneous. Since the value is squared, multiply twice.
slopSquare *= mAmbiguousGestureMultiplier * mAmbiguousGestureMultiplier;
}
if (distance > slopSquare) {
recordGestureClassification(
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
mAlwaysInTapRegion = false;
mHandler.removeMessages(TAP);
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
}
int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
if (distance > doubleTapSlopSquare) {
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPress) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendMessage(
mHandler.obtainMessage(
LONG_PRESS,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS,
0 ));
}
break;
case MotionEvent.ACTION_UP:
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
// Finally, give the up event of the double-tap
recordGestureClassification(
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mInLongPress) {
mHandler.removeMessages(TAP);
mInLongPress = false;
} else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
recordGestureClassification(
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
handled = mListener.onSingleTapUp(ev);
if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
mDoubleTapListener.onSingleTapConfirmed(ev);
}
} else if (!mIgnoreNextUpEvent) {
// A fling must travel the minimum tap distance
final VelocityTracker velocityTracker = mVelocityTracker;
final int pointerId = ev.getPointerId(0);
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final float velocityY = velocityTracker.getYVelocity(pointerId);
final float velocityX = velocityTracker.getXVelocity(pointerId);
if ((Math.abs(velocityY) > mMinimumFlingVelocity)
|| (Math.abs(velocityX) > mMinimumFlingVelocity)) {
handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
}
}
if (mPreviousUpEvent != null) {
mPreviousUpEvent.recycle();
}
// Hold the event we obtained above - listeners may have changed the original.
mPreviousUpEvent = currentUpEvent;
if (mVelocityTracker != null) {
// This may have been cleared when we called out to the
// application above.
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mIsDoubleTapping = false;
mDeferConfirmSingleTap = false;
mIgnoreNextUpEvent = false;
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
case MotionEvent.ACTION_CANCEL:
cancel();
break;
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
}
return handled;
}