本文主要是介绍Android手势检测——GestureDetector全面分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
http://blog.csdn.net/totond/article/details/77881180
在很多视频播放器中,都存在使用不同的手势来控制进度、亮度\音量和暂停播放等功能。Android提供了一个GestureDetector来帮助我们识别一些基本的触摸手势(还有ScaleGestureDetector可以识别缩放手势),让我们很方便地实现手势控制功能。下面我们就来学习一下GestureDetector的使用和通过源码(Android7.0)来分析一下它的实现,让我们对触摸事件处理的理解更加深入。
GestureDetector介绍
Detector的意思就是探测者,所以GestureDetector就是用来监听手势的发生。它内部有3个Listener接口,用来回调不同类型的触摸事件,用一个简略的类图来显示:
里面这些接口的方法,就是相应触摸事件的回调,实现了这些方法,就能实现传入触摸事件之后做出相应的回调。
一些回调接口:
1.OnGestureListener,这个Listener监听一些手势,如单击、滑动、长按等操作:
- onDown(MotionEvent e):用户按下屏幕的时候的回调。
- onShowPress(MotionEvent e):用户按下按键后100ms(根据Android7.0源码)还没有松开或者移动就会回调,官方在源码的解释是说一般用于告诉用户已经识别按下事件的回调(我暂时想不出有什么用途,因为这个回调触发之后还会触发其他的,不像长按)。
- onLongPress(MotionEvent e):用户长按后(好像不同手机的时间不同,源码里默认是100ms+500ms)触发,触发之后不会触发其他回调,直至松开(UP事件)。
- onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY):手指滑动的时候执行的回调(接收到MOVE事件,且位移大于一定距离),e1,e2分别是之前DOWN事件和当前的MOVE事件,distanceX和distanceY就是当前MOVE事件和上一个MOVE事件的位移量。
- onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY):用户执行抛操作之后的回调,MOVE事件之后手松开(UP事件)那一瞬间的x或者y方向速度,如果达到一定数值(源码默认是每秒50px),就是抛操作(也就是快速滑动的时候松手会有这个回调,因此基本上有onFling必然有onScroll)。
- onSingleTapUp(MotionEvent e):用户手指松开(UP事件)的时候如果没有执行onScroll()
和onLongPress()
这两个回调的话,就会回调这个,说明这是一个点击抬起事件,但是不能区分是否双击事件的抬起。
2.OnDoubleTapListener,这个Listener监听双击和单击事件。
- onSingleTapConfirmed(MotionEvent e):可以确认(通过单击DOWN后300ms没有下一个DOWN事件确认)这不是一个双击事件,而是一个单击事件的时候会回调。
- onDoubleTap(MotionEvent e):可以确认这是一个双击事件的时候回调。
- onDoubleTapEvent(MotionEvent e):onDoubleTap()
回调之后的输入事件(DOWN、MOVE、UP)都会回调这个方法(这个方法可以实现一些双击后的控制,如让View双击后变得可拖动等)。
3.OnContextClickListener,很多人都不知道ContextClick是什么,我以前也不知道,直到我把平板接上了外接键盘——原来这就是鼠标右键。。。
- onContextClick(MotionEvent e):当鼠标/触摸板,右键点击时候的回调。
4.SimpleOnGestureListener,实现了上面三个接口的类,拥有上面三个的所有回调方法。
- 由于SimpleOnGestureListener不是抽象类,所以继承它的时候只需要选取我们所需要的回调方法来重写就可以了,非常方便,也减少了代码量,符合接口隔离原则,也是模板方法模式的实现。而实现上面的三个接口中的一个都要全部重写里面的方法,所以我们一般都是选择SimpleOnGestureListener。
ps:上面所有的回调方法的返回值都是boolean类型,和View的事件传递机制一样,返回true表示消耗了事件,flase表示没有消耗。
GestureDetector的使用
GestureDetector的使用很简单,因为它的功能就是定义为识别手势,所以使用的话就是输入完整的触摸事件(完整的意思就是用户所有的触摸操作都是输入给它。为什么要强调完整,因为我上一篇博客就是分享如何拦截子View的部分触摸事件),识别然后进行相应的回调:
private void init(Context context){mOnGestureListener = new MyOnGestureListener();mGestureDetector = new GestureDetector(context,mOnGestureListener);
setOnTouchListener(new OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {return mGestureDetector.onTouchEvent(event);}});setOnGenericMotionListener(new OnGenericMotionListener() {@Overridepublic boolean onGenericMotion(View v, MotionEvent event) {Log.d(TAG, "onGenericMotion: ");return mGestureDetector.onGenericMotionEvent(event);}});}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
如上面的代码,要使用OnGestureListener和OnDoubleTapListener里面的回调需要调用GestureDetector.onTouchEvent()
方法,而使用OnContextClickListener的话则是需要调用onGenericMotionEvent()
方法,注意一个是在onTouch()
方法一个是在onGenericMotion()
方法。
看完了上面一堆文字,其实你就会懂得如何使用GestureDetector了,这里有GestureDetector的实践。但是如果你想了解它的回调的时机为什么会是这样的,想具体了解它们的回调时机,可以继续看下去,下面是源码分析。
GestureDetector源码分析
1. 初始化处理
GestureDetector的源码接近800行,这在Android源码中已经算是比较短的了(毕竟注释也占一两百行了),所以说它的实现也不是很复杂的。从它的构造方法开始:
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);}private void init(Context context) {if (mListener == null) {throw new NullPointerException("OnGestureListener must not be null");}mIsLongpressEnabled = true;int touchSlop, doubleTapSlop, doubleTapTouchSlop;if (context == null) {touchSlop = ViewConfiguration.getTouchSlop();doubleTapTouchSlop = touchSlop; doubleTapSlop = ViewConfiguration.getDoubleTapSlop();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;}
- 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
可见GestureDetector的创建就是初始化一些属性,然后就是把对应的Listener设置好,还有初始化Handler,而这里的GestureHandler,是控制onShowPress()
,onLongPress(),
onSingleTapConfirmed()`回调的关键:
private class GestureHandler extends Handler {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case SHOW_PRESS:mListener.onShowPress(mCurrentDownEvent);break;case LONG_PRESS:dispatchLongPress();break;case TAP:if (mDoubleTapListener != null) {if (!mStillDown) {mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);} else {mDeferConfirmSingleTap = true;}}break;default:throw new RuntimeException("Unknown message " + msg); }}}private void dispatchLongPress() {mHandler.removeMessages(TAP);mDeferConfirmSingleTap = false;mInLongPress = true;mListener.onLongPress(mCurrentDownEvent);}
- 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
2. 输入处理
初始化完之后,就是看它的如何处理输入了,这是它的核心逻辑:
public boolean onTouchEvent(MotionEvent ev) {//检查事件输入的一致性,log出来一致性的信息,如:有事件只有up没有downif (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();//把所有还在触摸的手指的位置x,y加起来,后面求平均数,算出中心焦点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://...break;case MotionEvent.ACTION_POINTER_UP://...break;case MotionEvent.ACTION_DOWN://...break;case MotionEvent.ACTION_MOVE://...break;case MotionEvent.ACTION_UP://...break;case MotionEvent.ACTION_CANCEL:cancel();break;}//对未被处理的事件进行一次一致性检测if (!handled && mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);}return handled;}
- 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
- 65
- 66
- 67
- 68
- 69
上面的注释写得很清楚了,主要onTouchEvent()
的主要思路就是先对输入事件做出统一处理,提取一些共有的信息,如多个点同时触摸时候的中心焦点和滑动速度等,然后根据事件的分类做出相应的处理。
ps:InputEventConsistencyVerifier对输入事件进行的一致性检测的结果并不影响GestureDetector的运行,如果检测到一致性不符合的事件(只有UP事件而前面没有DOWN事件),就只会输出log告诉开发者。
2.1. DOWN事件处理
下面进入DOWN事件的处理:
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)) {mIsDoubleTapping = true;handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);handled |= mDoubleTapListener.onDoubleTapEvent(ev);} else {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;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);}
- 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
- 65
- 66
- 67
- 68
可见,对DOWN事件涉及:
- 处理单击判断:如果收到一次DOWN事件,而且前段时间没有DOWN事件的话,会发送一个延时的TAP信息,而一段时间(300ms)之后没有被取消的话,就执行GestureHandler里面的TAP单击确认操作。
- 处理双击判断:如果前面也有一次DOWN事件,而且也符合isConsideredDoubleTap()
的条件(第一次点击后没有移动超出范围,第二次点击也在附近),就可以确认双击,执行onDoubleTap()
和onDoubleTapEvent()
的回调。
- 处理长按判断:先看用户是否允许检测长按,然后就是发送一个延时的LONG_PRESS信息,如果到时候还没被取消的话就是回调长按方法了。
- 处理showPress判断:这个和长按差不多,就是时间(100ms)短了一点而已。
PS:handled是boolean变量,|=
符号是用符号右边的值跟左边的值进行或运算再赋值给左边。a |= b
等价于a = a | b
,这里使handled变量初始值为false,进行了多次|=
操作,一旦有结果是true的话,handled最后的值就是true,所有结果都是false最后才会false,onTouchEvent()
方法最后返回这个handled,就可以表示事件有没被处理,实现了对事件处理的封装。
2.2. MOVE事件处理
然后再看看对MOVE事件的处理:
case MotionEvent.ACTION_MOVE:if (mInLongPress || mInContextClick) {break;}final float scrollX = mLastFocusX - focusX;final float scrollY = mLastFocusY - focusY;if (mIsDoubleTapping) {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;
- 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
可见,对MOVE事件涉及:
- onDoubleTapEvent()
回调:只要确认是双击之后,mIsDoubleTapping
为true,除了长按,后面的MOVE事件都会只回调onDoubleTapEvent()
。
- onScroll()
回调:当MOVE不是长按,不是DoubleTapEvent之后,当移动距离大于一定距离之后,就会进入Scroll模式,然后两个MOVE事件的位移距离scrollX或者scrollY大于1px,都会调用onScroll()
。
2.3. UP事件的处理
接下来再看UP事件:
case MotionEvent.ACTION_UP:mStillDown = false;MotionEvent currentUpEvent = MotionEvent.obtain(ev);if (mIsDoubleTapping) {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) {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();}mPreviousUpEvent = currentUpEvent;if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}mIsDoubleTapping = false;mDeferConfirmSingleTap = false;mIgnoreNextUpEvent = false;mHandler.removeMessages(SHOW_PRESS);mHandler.removeMessages(LONG_PRESS);break;
- 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
可见,对MOVE事件涉及:
- onDoubleTapEvent()
回调:只要确认是双击之后,mIsDoubleTapping
为true,除了长按,后面的MOVE事件都会只回调onDoubleTapEvent()
。
- onSingleTapUp()
回调:DOWN事件之后没有MOVE,或者MOVE的距离没有超出范围,mAlwaysInTapRegion
才不会变成false,回调onSingleTapUp()
。
- onSingleTapConfirmed()
回调:从前面GestureHandler里面的TAP消息的实现可以看到:
case TAP:if (mDoubleTapListener != null) {if (!mStillDown) {mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);} else {mDeferConfirmSingleTap = true;}}break;
之前看过,TAP消息是延时(300ms)发送的,然而实际逻辑中,是抬起手指才算是点击,所以这里处理TAP的时候就不一定立刻调用onSingleTapConfirmed()
,而是判断手指是否松开了,是松开的话就立刻回调。如果还未松开那就把标志位mDeferConfirmSingleTap
设置为true,等到收到UP事件的时候再回调。
- onFling()
回调:当UP事件的速度大于一定速度时,就会回调onFling()
,至于mIgnoreNextUpEvent
参数,是只有鼠标右键点击的时候才会为true,具体看后面。
2.4. 多点触摸的处理
对于多个手指落下,前面的统一处理已经是把所有手指的坐标值加起来然后算平均值了,所以我们多根手指触摸的时候,其实滑动的实际点是这些手指的中心焦点。回看上面的MOVE事件处理,中心焦点的位置值FocusX和FocusY决定onScroll(onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY))
的后两个参数值,所以处理多点触控的话就使用它们比较方便。因为MotionEvent是使用数组装着当前屏幕上所有指针的动作的,使用前两个参数的话还要循环用getX(int pointerIndex)
和getY(int pointerIndex)
方法取出各个指针的值再自己处理。
case MotionEvent.ACTION_POINTER_DOWN:mDownFocusX = mLastFocusX = focusX;mDownFocusY = mLastFocusY = focusY;cancelTaps();break;case MotionEvent.ACTION_POINTER_UP:mDownFocusX = mLastFocusX = focusX;mDownFocusY = mLastFocusY = focusY;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;private void cancelTaps() {mHandler.removeMessages(SHOW_PRESS);mHandler.removeMessages(LONG_PRESS);mHandler.removeMessages(TAP);mIsDoubleTapping = false;mAlwaysInTapRegion = false;mAlwaysInBiggerTapRegion = false;mDeferConfirmSingleTap = false;mInLongPress = false;mInContextClick = false;mIgnoreNextUpEvent = 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
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
这是对多个手指的UP和DOWN事件处理,其实就是做一些取消操作而让多点触摸不影响单点触摸的应用,例如在多个手指落下的时候取消点击信息等。
2.5. ContextClick的处理
ContextClick的事件是由onGenericMotionEvent()
传入:
public boolean onGenericMotionEvent(MotionEvent ev) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0);}final int actionButton = ev.getActionButton();switch (ev.getActionMasked()) {case MotionEvent.ACTION_BUTTON_PRESS:if (mContextClickListener != null && !mInContextClick && !mInLongPress&& (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY|| actionButton == MotionEvent.BUTTON_SECONDARY)) {if (mContextClickListener.onContextClick(ev)) {mInContextClick = true;mHandler.removeMessages(LONG_PRESS);mHandler.removeMessages(TAP);return true;}}break;case MotionEvent.ACTION_BUTTON_RELEASE:if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY|| actionButton == MotionEvent.BUTTON_SECONDARY)) {mInContextClick = false;mIgnoreNextUpEvent = true;}break;}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
- 30
- 31
- 32
由此可以,当按键按下(ACTION_BUTTON_PRESS)的时候已经回调onContextClick()
。
总结
读完源码,总结出来的每个回调的调用时机如下表:
PS:除去onContextClick()
,因为它的按下鼠标右键时候是发出一系列的事件。
回调/输入事件 | DOWN事件 | MOVE事件 | UP事件 |
---|
onDown(MotionEvent e) | √ | × | × |
onShowPress(MotionEvent e) | √ | × | × |
onLongPress(MotionEvent e) | √ | × | × |
onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY) | × | √ | × |
onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY)) | × | × | √ |
onSingleTapUp(MotionEvent e) | × | × | √ |
onSingleTapConfirmed(MotionEvent e) | × | × | √ |
onDoubleTap(MotionEvent e) | √ | × | × |
onDoubleTapEvent(MotionEvent e) | √ | √ | √ |
从上面的分析可以看出,虽然GestureDetector能识别很多手势,但是也是不能满足所有的需求的,如滑动和长按之后松开没有回调(这个可以重写onTouch()
捕捉UP事件实现)、多点触控缩放手势的实现(这个可以用ScaleGestureDetector)等。
后话
有人问我看GestureDetector源码这么仔细有什么用,它又不是很常用的东西,网上随便一搜一堆资料。我的回答是因为我觉得要用一个东西的话,首先就是要搞清楚它能干什么,它的限制是什么,为什么要选择它,关于这些方面,网上的很多关于GestureDetector的资料都没有达到我想了解的程度,加上GestureDetector并不复杂,所以写下了这篇博客,这样就可以从源码层面上了解到它的回调是什么时候调用,有bug的时候也能更快的找出。
不管怎样,GestureDetector里面的SimpleOnGestureListener的设计,和对触摸事件的处理方式是很值得我学习的,记录分享至此,水平有限,如果错漏,欢迎指正和讨论。
这篇关于Android手势检测——GestureDetector全面分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!