在android中,经常需要使用双击来响应一些操作,此时就可以使用系统提供的GestureDetector类来实现。

在GestureDetector类中,定义了OnGestureListener 和 OnDoubleTapListener 两个接口,可以根据需要分别实现不同的接口。

Android GestureDescription 双击 安卓双击事件_GestureDetector


Android GestureDescription 双击 安卓双击事件_三击_02


其中 OnGestureListener 对象的注入是通过构造方法的方式,而 OnDoubleTapListener 对象的注入是通过set方法设置的,见如下图片。

Android GestureDescription 双击 安卓双击事件_GestureDetector_03

Android GestureDescription 双击 安卓双击事件_数组_04


(android也提供了一个实现这两个接口的类来简化开发,即SimpleOnGestureListener类,覆写时只实现关心的回调)

当实例化了GestureDetector后,并且持有了手势和双击接口对象后,就可以在识别到相关事件时回调接口对应的方法了。 但是事件如何来呢?

一般在接收onTouch事件的地方,调用GestureDetector的onTouch()方法,并传入MotionEvent参数即可,GestureDetector的onTouch方法会对事件进行识别并回调。

GestureDectector的ACTION_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)) {
                    // 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);
                }
            }
			...
			mCurrentDownEvent = MotionEvent.obtain(ev);
			...

当 mDoubleTapListener 不为null时,会先判断是否有tap消息,如果有则删除。
(因为tap消息的发送是一个延时消息,延时的时间长度是 DOUBLE_TAP_TIMEOUT。如果此时还有消息,则说明此次的down事件还在 DOUBLE_TAP_TIMEOUT 时间内,故要进一步分析是否为双击事件。 )

在经过了一次down事件后 mCurrentDownEvent 就不为空,而 mPreviousUpEvent 也会在第一次up事件时赋值,故也不为空。 如果 hadTapMessage 为 false,则说明第一次事件距离第二次超过了 DOUBLE_TAP_TIMEOUT 定义的时间了,不会认为是双击,而是两次单击了。 如果前面条件都满足了,接下来就要分析这几个event的关系来识别双击了,主要是event的触摸点相差不能太大,具体如下:

Android GestureDescription 双击 安卓双击事件_android_05


当符合这几个条件时,就会被识别为双击,从而回调相关的接口。

其实当时分析双击的接口,主要是想识别一下三击。 但是这个考虑的细节太多了,搞不好容易出bug,最后还是用了网络上比较常用的方法,即使用一个数组来记录event事件的时间,然后判断这几个事件时间间隔来判断是不是三击。

代码如下

Android GestureDescription 双击 安卓双击事件_GestureDetector_06


这里参考GestureDetector的源码,加入时event时间和距离的检测,避免不在同一个地方快速三击也会被识别三击的问题。

其中,使用了System.arraycopy的方法来数组元素的更新,其实就相当于是一个入队出队的效果,看了方法参数说明就很好理解了。该方法的参数说明如下:

/**
 * 
 * @param src 原数组
 * @param srcPos 从元数据的起始位置开始
 * @param dest 目标数组
 * @param destPos 目标数组的开始起始位置
 * @param length 要copy的数组的长度
 */
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

如上代码中arraycopy方法做的就是将第一个元素丢弃,并将后面的元素依次向前推,形成一个队列,保存最近的三次事件信息。 注意,MotionEvent是通过obtain的方式创建的,而obtain内部是通过复用的方式实现的,故可能存在Motion对象没变,但是其中的数据更新的问题。

上面的checkTripleTap是放在抠出的GestureDetector类的up事件中的,这样的方法并没有很好的将双击和三击区分开,被识别为三击前会选响应双击。 最后的一击通过removeMessage删除掉了,避免回调singleTapConfirmed接口,如果有需要也可以将show_press消息删除来避免这些延时操作的干挠。