在android中,经常需要使用双击来响应一些操作,此时就可以使用系统提供的GestureDetector类来实现。
在GestureDetector类中,定义了OnGestureListener 和 OnDoubleTapListener 两个接口,可以根据需要分别实现不同的接口。
其中 OnGestureListener 对象的注入是通过构造方法的方式,而 OnDoubleTapListener 对象的注入是通过set方法设置的,见如下图片。
(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的触摸点相差不能太大,具体如下:
当符合这几个条件时,就会被识别为双击,从而回调相关的接口。
其实当时分析双击的接口,主要是想识别一下三击。 但是这个考虑的细节太多了,搞不好容易出bug,最后还是用了网络上比较常用的方法,即使用一个数组来记录event事件的时间,然后判断这几个事件时间间隔来判断是不是三击。
代码如下
这里参考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消息删除来避免这些延时操作的干挠。