GestureDetector一部分源码分析
GestureDetector是android给我们提供的用户手势识别类。
GestureDetector关注三个接口OnGestureListener,OnDoubleTapListener ,OnContextClickListener(这个我们先不考虑这个应该是和鼠标之类的外接设备相关的)一个内部类SimpleOnGestureListener,以及两个函数GestureDetector构造函数和GestureDetector的onTouchEvent函数。
OnGestureListener接口管理的是一次触摸屏幕从MotionEvent.ACTION_DOWN -> … ->MotionEvent.ACTION_UP过程产生的事件回调接口。
...
public interface OnGestureListener {
// 手指按下的时候调用
boolean onDown(MotionEvent e);
// 短按(默认100毫秒)
void onShowPress(MotionEvent e);
// 单击手指抬起(MotionEvent.ACTION_UP)的时候触发
boolean onSingleTapUp(MotionEvent e);
/**
* 手指滑动的时候调用
* e1: MotionEvent.ACTION_DOWN对应的MotionEvent
* e2: 当前MotionEvent.ACTION_MOVE对应的MotionEvent
* distanceX: 每次scroll x方向变化的距离
* distanceY: 每次scroll y方向变化的距离
*/
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
// 长按(100+500=600毫秒)注:一般响应了onLongPress那么onShowPress也会响应的
void onLongPress(MotionEvent e);
/**
* 在手指抬起的时候如果滑动的速度不管是x方向还是y方向超过了某个值 (50dip/s)就会触发
* e1: MotionEvent.ACTION_DOWN对应的MotionEvent
* e2: MotionEvent.ACTION_UP对应的MotionEvent
* velocityX: x方向速度
* velocityY: y方向速度
*/
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
}
...
OnDoubleTapListener接口管理的是满足双击条件产生的事件。
...
public interface OnDoubleTapListener {
// 单击事件调用,只有确认了不是双击事件,不是长按事件,不是滑动事件 该函数调用
boolean onSingleTapConfirmed(MotionEvent e);
// 满足双击事件,在第二次单击MotionEvent.ACTION_DOWN的时候触发这个事件
boolean onDoubleTap(MotionEvent e);
// 满足双击事件,第二次单击过程中的产生的MotionEvent回调(第二次轻触过程MotionEvent.ACTION_DOWN -> ... ->MotionEvent.ACTION_UP一系列事件)
boolean onDoubleTapEvent(MotionEvent e);
}
...
OnContextClickListener 和鼠标之类的外接设备相关。
SimpleOnGestureListener 内部类实现了OnGestureListener,OnDoubleTapListener ,OnContextClickListener三个接口的class 这样做的好处是 在使用SimpleOnGestureListener的时候只需要重写我们关心的回调方法就好了。不用重写所有的方法。
接下来按照GestureDetector的使用流程来分析,一般使用GestureDetector的步骤都是先构造出GestureDetector对象,然后就是在我们要监听的View设置setOnTouchListener在onTouch里面调用GestureDetector的onTouchEvent函数。
GestureDetector构造函数
...
public GestureDetector(OnGestureListener listener, Handler handler) {
this(null, listener, handler);
}
public GestureDetector(OnGestureListener listener) {
this(null, listener, null);
}
public GestureDetector(Context context, OnGestureListener listener) {
this(context, listener, null);
}
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
if (handler != null) {
mHandler = new GestureHandler(handler);
} else {
mHandler = new GestureHandler();
}
mListener = listener;
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener((OnDoubleTapListener) listener);
}
if (listener instanceof OnContextClickListener) {
setContextClickListener((OnContextClickListener) listener);
}
init(context);
}
public GestureDetector(Context context, OnGestureListener listener, Handler handler,
boolean unused) {
this(context, listener, handler);
}
...
不管调用的哪个构造函数最后都是走的一个,
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
if (handler != null) {
mHandler = new GestureHandler(handler);
} else {
mHandler = new GestureHandler();
}
mListener = listener;
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener((OnDoubleTapListener) listener);
}
if (listener instanceof OnContextClickListener) {
setContextClickListener((OnContextClickListener) listener);
}
init(context);
}
三个参数,context没什么说的,listener 设置回调这里注意如果这里传进来的是SimpleOnGestureListener对象那么两个instanceof都是会走的setOnDoubleTapListener函数和setContextClickListener函数都会调用到,handler GestureHandler对象,因为SHOW_PRESS,LONG_PRESS,TAP三个对应的事件都是要靠一定的延时才能判断是否发生所以借助handler 的 sendEmptyMessageDelayed,可以看到构造函数就干了三件事,一个是handler,一个是设置listner 也就是前面说的三个interface,最后一个就是init(context)。接着就应该瞧下handler和init(context)。
GestureHandler 代码如下
private class GestureHandler extends Handler {
GestureHandler() {
super();
}
GestureHandler(Handler handler) {
super(handler.getLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PRESS:
mListener.onShowPress(mCurrentDownEvent);
break;
case LONG_PRESS:
dispatchLongPress();
break;
case TAP:
// If the user's finger is still down, do not count it as a tap
if (mDoubleTapListener != null) {
if (!mStillDown) {
mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
} else {
mDeferConfirmSingleTap = true;
}
}
break;
default:
throw new RuntimeException("Unknown message " + msg); //never
}
}
}
handleMessage 里面三种消息SHOW_PRESS(短按对应的事件), LONG_PRESS(长按对应的事件), TAP(确认单击事件)。为什么会有这个GestureHandler。因为SHOW_PRESS,LONG_PRESS,TAP三个对应的事件都是要靠一定的延时才能判断是否发生,不能在每个MotionEvent的时候决定是否发生。
看下case TAP:的情况。mStillDown如果不是按下的状态(手指已经离开了),并且TAP还在队列里面。则调用onSingleTapConfirmed。如果还是按下的状态并且TAP还在消息队列里面则推迟判断(会在MotionEvent.ACTION_UP的时候做进一步的处理)。
init函数 做一些初始化的操作,初始化一些变量用做判断的数据。
private void init(Context context) {
if (mListener == null) {
throw new NullPointerException("OnGestureListener must not be null");
}
mIsLongpressEnabled = true;
// Fallback to support pre-donuts releases
int touchSlop, doubleTapSlop, doubleTapTouchSlop;
if (context == null) {
//noinspection deprecation
touchSlop = ViewConfiguration.getTouchSlop();
doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
//noinspection deprecation
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
} else {
final ViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration.getScaledTouchSlop();
doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
doubleTapSlop = configuration.getScaledDoubleTapSlop();
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
}
mTouchSlopSquare = touchSlop * touchSlop;
mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
}
如果mListener为null对不起异常了。
mIsLongpressEnabled: 是否支持长按操作。
mMinimumFlingVelocity(50dip/s): fling判断移动的最小速度。
mMaximumFlingVelocity(8000dip/s): fling判断移动的最大速度;
mTouchSlopSquare(8 * 8): 用来判断是否开始scroll。
mDoubleTapTouchSlopSquare(8 * 8): 判断双击的时候用到,第一个单击的时候产生了MotionEvent.ACTION_MOVE,并且move的距离超过了这个值 就不认为是双击事件。
mDoubleTapSlopSquare(100 * 100): 判断双击的时候用到,两次单击范围要在这个值之内。否则不算是双击事件。
接下来最重要的函数onTouchEvent 我们就关心三个action分别是MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP
public boolean onTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
}
final int action = ev.getAction();
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;
// 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;
// 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;
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+ TAP_TIMEOUT + LONGPRESS_TIMEOUT);
}
mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;
case MotionEvent.ACTION_MOVE:
if (mInLongPress || mInContextClick) {
break;
}
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) {
// Give the move events of the 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);
if (distance > mTouchSlopSquare) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
mAlwaysInTapRegion = false;
mHandler.removeMessages(TAP);
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
}
if (distance > mDoubleTapTouchSlopSquare) {
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
}
break;
case MotionEvent.ACTION_UP:
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
// Finally, give the up event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mInLongPress) {
mHandler.removeMessages(TAP);
mInLongPress = false;
} else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
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;
}
首先是case MotionEvent.ACTION_DOWN动作的处理过程。
直接跳到第65行,case MotionEvent.ACTION_DOWN:部分 if mDoubleTapListener 不等于null(如果设置了OnDoubleTapListener)这个好理解在构造函数的传入了SimpleOnGestureListener的对象或者调用了setOnDoubleTapListener()函数,总的来说case MotionEvent.ACTION_DOWN做了三件事情
1. 判断队列里面是否有TAP message,如果有则remove掉,TAP的延时判断时间是DOUBLE_TAP_TIMEOUT(300毫秒) 换句话就是如果在前一次单击到下一次单击的时间在300毫秒之内前一次的TAP舍弃掉。
2. 判断是否满足双击条件。两种情况满足和不满足。
如果满足:mIsDoubleTapping = ture记录当前是双击,并且调用onDoubleTap onDoubleTapEvent两个回调函数。
如果不满足:说明是一个全新的按键事件了和前一次按键没关系了 准备判断TAP。
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); send TAP message delay 时间 DOUBLE_TAP_TIMEOUT(300毫秒)
继续看下双击事件的判断条件if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev))四个条件
1). mCurrentDownEvent表示上一次的ACTION_DOWN的MotionEvent事件(注意这里还没有赋值成当前的MotionEvent)。保证有第一次的点击。
2). mPreviousUpEvent表示上一次的MotionEvent.ACTION_UP的MotionEvent事件。保证有第一次的点击。
3). hadTapMessage 队列里面有TAP message 说明两次单击的时间间隔在300毫秒之内。
4). isConsideredDoubleTap() 通过判断第一次单击down动作到up动作是不是满足双击的条件(时间间隔, 距离)的。
isConsideredDoubleTap 两个参数第一次单击时候的down对应的MotionEvent 第一次单击时候up对应的MotionEvent事件。
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
MotionEvent secondDown) {
if (!mAlwaysInBiggerTapRegion) {
return false;
}
final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
return false;
}
int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
}
第3行:mAlwaysInBiggerTapRegion表示在第一次单击的过程中产生了ACTION_MOVE事件,并且move的距离超过了一定范围(这个范围就是init函数中说的mDoubleTapTouchSlopSquare) mAlwaysInBiggerTapRegion赋值的地方在case MotionEvent.ACTION_MOVE过程里面处理。
第7行:通过第一次单击从down到up的过程的时间来判断是不是属于双击事件(40~300毫秒之内)。
第13行:通过第一次单击从down和up两个动作对应的MotionEvent是否在一定的范围之内(这个范围就是init函数中说的mDoubleTapSlopSquare)
总结下双击事件产生的条件 四个条件。
1. 两次单击的时间间隔在300毫秒之内
2. 第一次单击如果产生了ACTION_MOVE事件,则ACTION_MOVE move的距离不能超过mDoubleTapTouchSlopSquare(默认是8)
3. 第一次单击down到up的过程事件在DOUBLE_TAP_MIN_TIME ~ DOUBLE_TAP_TIMEOUT(40毫秒~300毫秒)之间
4. 第一次单击down和up对应的MotionEvent距离 不能超过mDoubleTapSlopSquare(100dip)
接着onTouchEvent第95行 mIsLongpressEnabled如果支持长按事件 准备LONG_PRESS判断。 mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT + LONGPRESS_TIMEOUT); send LONG_PRESS message delay TAP_TIMEOUT + LONGPRESS_TIMEOUT(100 + 500 = 600毫秒)
第100行 准备SHOW_PRESS判断,mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); send SHOW_PRESS message delay TAP_TIMEOUT(100毫秒)
第101行 调用onDown的call back 可以看到每次down的动作都会调用该函数。
其次是case MotionEvent.ACTION_MOVE动作的处理过程。
第104行 case MotionEvent.ACTION_MOVE 对ACTION_MOVE 事件的处理。
第105行 如果当前产生了long click事件 或者 content click事件 直接break ,产生了这两个事件就不让产生onScroll事件,也就是说不让回调onScroll函数。
第110行 if (mIsDoubleTapping) 如果当前是双击事件 则回调OnDoubleTapListener.onDoubleTapEvent()
注:OnDoubleTapListener.onDoubleTapEvent() 的调用是在第二次单击从按下到抬起过程中产生的触摸事件的回调,这个函数会多次调用(至少得有ACTION_DOWN和ACTION_UP对应的MotionEvent事件 )。
第113~128行 mAlwaysInTapRegion 表示当前触点和ACTION_DOWN时候的触点相比是否还在单击的范围之内(<8dip) 接下来就是具体的判断如果大于mTouchSlopSquare 则回调OnGestureListener.onScroll()。同时把TAP,SHOW_PRESS,LONG_PRESS从队列中move掉。可以看出如果产生了OnGestureListener.onScroll()就不会产生OnGestureListener.onShowPress(), OnGestureListener.onLongPress(), OnDoubleTapListener.onSingleTapConfirmed()对应的回调了。
同时判断distance > mDoubleTapTouchSlopSquare 为双击的判断做好准备。前面有提到过双击判断的条件有一点是 第一次单击如果产生了ACTION_MOVE事件,则ACTION_MOVE move的距离不能超过mDoubleTapTouchSlopSquare(默认是8) 就是在这里设置的。
第129~133行 两个触点距离大于1dip则回调OnGestureListener.onScroll() 方法。
最后是case MotionEvent.ACTION_UP 动作的处理过程。
第136行:MotionEvent.ACTION_UP 事件处理。一堆if的判断,如果是双击事件则是执行OnDoubleTapListener.onDoubleTapEvent(),如处理了long click 则把TAP从消息队里里面move掉。如果是单击事件回调OnGestureListener.onSingleTapUp(),同时判断是否要在ACTION_UP的时候回调OnDoubleTapListener.onSingleTapConfirmed()。最后一个if通过x或者y方向的移动速度判断是否满足OnGestureListener.onFling()的回调。
最后把SHOW_PRESS,LONG_PRESS从消息队列里面移除掉。
整个onTouchEvent就结束了。
总结下onTouchEvent对MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP三个case的处理
1. MotionEvent.ACTION_DOWN:
1). 判断是否满足双击的条件,如果满足回调onDoubleTap() onDoubleTapEvent()两个函数。如果不满足发送TAP延时消息。
2). 记录 mDownFocusX, mDownFocusY, mCurrentDownEvent 变量的值。
3). 发送SHOW_PRESS,LONG_PRESS 延时消息。
4). 回调onDown 每次都会调用
MotionEvent.ACTION_MOVE
1). 如果已经处理了long click 事件或者 context click事件。直接break。
2). 判断当前是不是双击的情况 如果是直接调用onDoubleTapEvent()函数。
3). 判断move的值是否大于8dip如果大于准备调用onScroll() 同时remoe掉TAP,SHOW_PRESS,LONG_PRESS消息。MotionEvent.ACTION_UP:
1). 判断当前是不是双击的情况 如果是直接调用onDoubleTapEvent()函数。
2). 判断是不是处理了long click 事件。
3). 判断是不是还是TAP事件,是就调用onSingleTapUp 同时判断是不是要调用onSingleTapConfirmed。
4). 判断是不是fling 需不需要调用onFling。
5). remove 掉 SHOW_PRESS,LONG_PRESS。
通过分析GestureDetector代码总结出一些动作产生的回调函数。如下
1. 单击
onDown -> onSingleTapUp -> onSingleTapConfirmed
2. 短按
onDown -> onShowPress -> onSingleTapUp -> onSingleTapConfirmed
3. 长按
onDown -> onShowPress -> onLongPress
4. 滑动
onDown -> onScroll -> onFling(这个可能有也可能没有决定权在滑动的速度)
5. 双击
onDown -> onSingleTapUp -> onDoubleTap - > onDoubleTapEvent(down) -> onDown -> onDoubleTapEvent(up)
6. 双击但是第二次晚点抬起手指。
onDown -> onSingleTapUp -> onDoubleTap - > onDoubleTapEvent(down) -> onDown -> onDoubleTapEvent(move)不止一个 -> onShowPress -> onDoubleTapEvent(move)不止一个 -> onLongPress -> onDoubleTapEvent(up)
在提点额外的,如果在使用GestureDetector的过程中,onDown的call back中没有return true,而是return false。会出现什么结果呢。通过上面的分析onDown的call back是在onTouchEvent 的MotionEvent.ACTION_DOWN中回调的,如果我们在MotionEvent.ACTION_DOWN的时候返回false那么 MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP不会传过来的。
所以如果我们在onDown的call back中return false。不管什么触摸动作回调顺序都是onDown -> onShowPress -> onLongPress。