我正在参加「掘金·启航计划」
用户触摸屏幕时会产生很多手势,在 Android 中,监听屏幕触摸事件也有好几种方法,比如 OnTouchListener,onTouchEvent,OnClickListener,GestureDetector 等等,这些方法实现细节和功能有所不同,下面就来理清这些吧!
事件分发机制
事件分发的三个主要对象是:Activity,ViewGroup,View。一个事件产生之后,都是先传给 Activity,再传给 ViewGroup,最后传给 View。
其中有三个重要的方法:
- dispatchTouchEvent:分发事件
- onInterceptTouchEvent:判断是否拦截某个事件,只存在于 ViewGroup
- onTouchEvent:处理事件
当用户点击了屏幕,事件先传递到 Activity 中,Activity 通过它的 dispatchTouchEvent 将事件分发到 phoneWindow,phonewindow 有个内部类 DecorView,DecorView 会调用 dispatchTouchEvent 去进行事件分发,如果不拦截事件,就会下传到 Rootview,Rootview 在 dispatchTouchEvent 内部调用 onInterceptTouchEvent 去判断是否拦截,不拦截就会把事件分发给下一个 Viewgroup,拦截就在 onTouchEvent 进行处理并返回 true,Viewgroup 中也是一样,最后事件传递到 View,View 是最底层控件,不会有onInterceptTouchEvent,它的选择就只有处理和不处理,处理就在 onTouchEvent 进行处理并返回 true,不处理的话事件也不会被销毁,View 这时会把事件回传,经过上述流程后回传给 Activity,如果此时 Activity 还不处理,那么这个事件才会被销毁。

所以,Activity 和 View 是没有 onInterceptTouchEvent 这个方法的,因为 Activity 是处于分发机制的最顶端,如果一开始就把事件拦截了,那么会导致整个屏幕都无法响应用户的操作,而 View 处于事件分发的最末端,它不需要拦截,事件分发到 View 的时候,View 能处理就处理,不处理就返回给他的父容器。
OnTouchListener 和 onTouchEvent
OnTouchListener 接口的方法 onTouch,它是获取某一个控件的触摸事件,使用时必须绑定到控件,当一个 View 绑定了 OnTouchListener 之后,当有 Touch 事件触发时,就会调用 onTouch 方法。
onTouchEvent 是屏幕事件的处理方法,是获取的对屏幕的各种操作,属于一个宏观的屏幕触摸监控。OnTouchListener 是执行在 onTouchEvent 之前的,如果在 OnTouchListener 的 onTouch 函数中返回 true,则表示消费了该事件,该事件不会传到 onTouchEvent 中。
OnLongClickListener 和 OnClickListener
OnClickListener 和 OnLongClickListener 都是在 onTouchEvent 中进行处理的,在 onTouchEvent 中返回 true 时才能判断该触摸事件是不是点击,如果在 onTouchEvent 中返回 false 则不处理事件,这时的 OnClickListener 和 OnLongClickListener 都是无效的。那 OnClickListener 和 OnLongClickListener 的区别在于 OnLongClickListener 在 onTouchEvent 判断是 ACTION_DOWN 事件并且按压持续一定时间后就会触发 OnLongClickListener,但之后要等到 onTouchEvent 判断是 ACTION_UP 事件时才会触发 OnClickListener,所以 OnLongClickListener 优先级是大于 OnClickListener 的,当然如果在 OnLongClickListener 的 onLongClick 函数中返回 true 的话就不会触发后面的 OnClickListener 了。
总的来说,优先级是:OnTouchListener > onTouchEvent > OnLongClickListener > OnClickListener,当然,这个需要具体问题具体分析,毕竟随着返回值的不同,有些函数可能都不会调用。
GestureDetecor
用户触摸事件,一般使用上面的那些方法就可以解决了,但是如果需要处理一些复杂的手势,用这些接口就会感觉有点麻烦,需要自己根据用户触摸的轨迹去判断是什么手势,这时就要靠 GestureDetector 了。
首先,实现 OnGestureListener 接口中的方法,里面包含着各种事件,如下所示
class MyGestureListener : GestureDetector.OnGestureListener {
/**
* 每按一下屏幕立即触发,如果要响应操作需要返回 true
*/
override fun onDown(e: MotionEvent): Boolean {
return true
}
/**
* 用户按下屏幕后未松手前长按触发
*/
override fun onShowPress(e: MotionEvent) {
}
/**
* 单击抬起时触发,返回值表示事件是否被处理
*/
override fun onSingleTapUp(e: MotionEvent): Boolean {
return false
}
/**
* 用户按下后在屏幕上滑动时触发,返回值表示事件是否被处理
* @param e1:开始滑动第一次按下操作,也就是 ACTION_DOWN
* @param e2:触发当前 onScroll 方法的 ACTION_MOVE
* @param distanceX:自上次调用 onScroll 以来沿 X 轴滚动的距离
* @param distanceY:自上次调用 onScroll 以来沿 Y 轴滚动的距离
*/
override fun onScroll(
e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float
): Boolean {
return false
}
/**
* 长按事件触发
*/
override fun onLongPress(e: MotionEvent) {
}
/**
* 用户快速滑动屏幕时触发,返回值表示事件是否被处理
* @param e1:开始快速滑动第一次按下操作,也就是 ACTION_DOWN
* @param e2:触发当前 onFling 方法的 ACTION_MOVE
* @param velocityX:X 轴上的移动速度,像素/秒
* @param velocityY:Y 轴上的移动速度,像素/秒
*/
override fun onFling(
e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float
): Boolean {
return false
}
}
onFling 在用户快速拖动并抬起手指发生时执行,通常用来实现翻页效果,而 onScroll,只要手指移动就会执行,可以用来实现放大缩小。
创建 GestureDetector 类的实例
val myGestureDetector = GestureDetector(context, MyGestureListener())
可以在 onTouch 方法中拦截事件处理,将控制权交给 GestureDector
button.setOnTouchListener { _, event ->
return@setOnTouchListener myGestureDetector.onTouchEvent(event)
}
但是这样需要重写这么多方法,显然不好用,所以一般开发中我们都会使用 SimpleOnGestureListener,它结合了 OnGestureListener,OnDoubleTapListener 和 OnContextClickListener 中的所有方法,可以有选择的去重写其中的方法。
class MyGestureListener : GestureDetector.SimpleOnGestureListener() {
/**
* 单击事件,如果只点击一次,系统等待一段时间后没有收到第二次点击则判断为单击
* 与 onSingleTapUp 的区别在于:
* onSingleTapUp 只要手抬起就会执行,如果是双击的话,onSingleTapConfirmed 不会执行。
*/
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
return super.onSingleTapConfirmed(e)
}
/**
* 双击事件
*/
override fun onDoubleTap(e: MotionEvent): Boolean {
return super.onDoubleTap(e)
}
/**
* 双击之间发生的其他动作
*/
override fun onDoubleTapEvent(e: MotionEvent): Boolean {
return super.onDoubleTapEvent(e)
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
return super.onSingleTapUp(e)
}
override fun onLongPress(e: MotionEvent) {
super.onLongPress(e)
}
}