0%

事件分发,谁会响应点击事件?

事件分发,谁会响应点击事件?

一、背景

场景是在一个Activity中有一个ViewGroup(比如Framelayout),ViewGroup中有一个子View,手指在子view上按下然后滑动直到ViewGroup区域外松开手指,如果他们两个都设置了click监听,那么谁会响应到点击事件呢?为什么?

二、分析

先来回顾一下事件分发机制,首先需要了解三个非常重要的方法dispatchTouchEvent onInterceptTouchEvent onTouchEvent

dispatchTouchEvent:事件分发逻辑,返回结果依赖当前View的onTouchEvent和下级view的dispatchTouchEvent方法结果,表示是否消耗此事件

onInterceptTouchEvent:在dispathTouchEvent方法内调用表示是否拦截事件,如果当前view拦截了某个事件,那么在同一个事件序列中此方法不会被再次调用

onTouchEvent:用来处理点击事件表示是否消耗当前事件,如果不消耗,在同一个事件序列中,无法再次接收到事件

通过一段经典伪代码来展示他们之间的关系

public boolean dispatchTouchEvent(MotionEvent event){
boolean consume = false;
if(onInterceptToucheEvent(event)){
consume = onTouchEvent(event);
}else{
consume = child.dispatchTouchEvent(event);
}
return consume;
}

实际代码细节还有很多,为了更好理解和解决实际开发中的问题,包括上面提到谁会响应点击事件诸如此类问题。下面先总结事件分发的规则在通过代码源码分析验证

2.1分发规则

分发规则其实是上面经典的伪代码的体现以及细节补充

  1. 分发规则是针对某一次的事件序列,事件序列是指手指按下触发到松开手指过程中产生的down、up和move事件
  2. 正常情况下一个事件序列只能被一个view消耗,一旦view开始处理事件消耗了down事件同一个事件序列中的后续事件都会交给它处理,特殊情况是代码调用onTouchEvent强制传递给其他View
  3. 如果一个view不消耗down事件,那么这个事件序列中的其他事件也不会在交给它处理并且事件会重新被传递给父View同时父View的onTouchEvent方法会被调用
  4. 如果一个VIew不消耗down事件以外的其他事件,那么这个事件会消失,不会传递给父View,父view的onTouchEvent方法不会被调用,最终这些事件会传递给Activity处理
  5. 一旦viewgroup拦截事件,同一个事件序列中后续事件都会交给它来处理,并且onInterceptTouchEvent不会被再次调用
  6. viewGourp默认不拦截事件,它的onInterceptTouchEvent默认返回false
  7. view没有onInterceptTouchEvent方法,事件传递给View后它的onTouchEvent方法会被调用
  8. view的onTouchEvent默认都会消耗事件返回true,除非view是不可点击的,clickable和longClickable同时为false。不受enable属性限制
  9. view的click能响应的条件是可以点击并且接收到了down和up事件
  10. 通过requestDisallowInterceptTouchEvent可以在子View中干预父View的事件分发过程但down事件除外

2.2源码解析

源码解析是对分发规则的验证,同时对分发规则中更多细节逻辑的补充说明

以Android 12系统源代码为例先来看ViewGroup的dispatchTouchEvent

/Users/hezd/Library/Android/sdk/platforms/android-31/android.jar!/android/view/ViewGroup.class

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 忽略其他无关代码...
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// 忽略其他无关代码...
}

从上面这部分代码可以看出

2.3案例解析:谁会响应点击事件

三、代码验证