[转]Android手势检测——GestureDetector全面分析

 炎之铠   2018-05-05 08:21   191 人阅读  0 条评论

前言

在很多视频播放器中,都存在使用不同的手势来控制进度、亮度\音量和暂停播放等功能。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的使用很简单,因为它的功能就是定义为识别手势,所以使用的话就是输入完整的触摸事件(完整的意思就是用户所有的触摸操作都是输入给它。),识别然后进行相应的回调:

如上面的代码,要使用OnGestureListener和OnDoubleTapListener里面的回调需要调用 GestureDetector.onTouchEvent()方法,而使用OnContextClickListener的话则是需要调用 onGenericMotionEvent()方法,注意一个是在 onTouch()方法一个是在 onGenericMotion()方法。

看完了上面一堆文字,其实你就会懂得如何使用GestureDetector了,这里有GestureDetector的实践。但是如果你想了解它的回调的时机为什么会是这样的,想具体了解它们的回调时机,可以继续看下去,下面是源码分析。

GestureDetector源码分析

1. 初始化处理

GestureDetector的源码接近800行,这在Android源码中已经算是比较短的了(毕竟注释也占一两百行了),所以说它的实现也不是很复杂的。从它的构造方法开始:

可见GestureDetector的创建就是初始化一些属性,然后就是把对应的Listener设置好,还有初始化Handler,而这里的GestureHandler,是控制 onShowPress()onLongPress()onSingleTapConfirmed()`回调的关键:

2. 输入处理

初始化完之后,就是看它的如何处理输入了,这是它的核心逻辑:

上面的注释写得很清楚了,主要 onTouchEvent()的主要思路就是先对输入事件做出统一处理,提取一些共有的信息,如多个点同时触摸时候的中心焦点和滑动速度等,然后根据事件的分类做出相应的处理。

ps:InputEventConsistencyVerifier对输入事件进行的一致性检测的结果并不影响GestureDetector的运行,如果检测到一致性不符合的事件(只有UP事件而前面没有DOWN事件),就只会输出log告诉开发者。

2.1. DOWN事件处理

下面进入DOWN事件的处理:

可见,对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事件的处理:

可见,对MOVE事件涉及:
onDoubleTapEvent()回调:只要确认是双击之后, mIsDoubleTapping为true,除了长按,后面的MOVE事件都会只回调 onDoubleTapEvent()
onScroll()回调:当MOVE不是长按,不是DoubleTapEvent之后,当移动距离大于一定距离之后,就会进入Scroll模式,然后两个MOVE事件的位移距离scrollX或者scrollY大于1px,都会调用 onScroll()

2.3. UP事件的处理

接下来再看UP事件:

可见,对UP事件涉及:
onDoubleTapEvent()回调:只要确认是双击之后, mIsDoubleTapping为true,除了长按,后面的MOVE事件都会只回调 onDoubleTapEvent()
onSingleTapUp()回调:DOWN事件之后没有MOVE,或者MOVE的距离没有超出范围, mAlwaysInTapRegion才不会变成false,回调 onSingleTapUp()
onSingleTapConfirmed()回调:从前面GestureHandler里面的TAP消息的实现可以看到:

之前看过,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)方法取出各个指针的值再自己处理。

这是对多个手指的UP和DOWN事件处理,其实就是做一些取消操作而让多点触摸不影响单点触摸的应用,例如在多个手指落下的时候取消点击信息等。

2.5. ContextClick的处理

ContextClick的事件是由 onGenericMotionEvent()传入:

由此可以,当按键按下(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)等。

本文地址:https://www.jvxiang.com/转android手势检测-gesturedetector全面分析.html
温馨提示:文章内容系作者个人观点,不代表聚享阁对观点赞同或支持。
版权声明:本文为转载文章,来源于 炎之铠 ,版权归原作者所有,欢迎分享本文,转载请保留出处!
PREVIOUS:已经是最后一篇了
NEXT:已经是最新一篇了

 发表评论


表情