0.事件分发总述:
| 类型 | 相关方法 | ViewGroup | View |
|---|---|---|---|
| 事件分发 | dispatchTouchEvent | √ | √ |
| 事件拦截 | onInterceptTouchEvent | √ | X |
| 事件消费 | onTouchEvent | √ | √ |
View是没有onInterceptTouchEvent方法的,ViewGroup才有onInterceptTouchEvent方法
1.View的事件分发
View的事件分发从View#dispatchTouchEvent方法开始。在dispatchTouchEvent中,管理了众多事件的监听器和onTouchEvent方法。
我们知道可以给View设置单击事件(onClick),长按事件(onLongClick),触摸事件(onTouch),而且View自身也有onTouchEvent方法。这些方法都是通过dispatchTouchEvent来进行管理的。
View相关的各个方法的调用顺序:
onTouchListener > onTouchEvent > onLongClickListener > onClickListener
在dispatchTouchEvent方法内部,如果mOnTouchListener.onTouch(this, event)为真,dispatchTouchEvent()直接返回true,否则执行onTouchEvent()。因此,onTouch()比onClick()优先级更高
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener.onTouch(this, event)) {
return true;
} else if (onTouchEvent(event)) {
return true;
}
return false;
}
接着在onTouchEvent()内部,
当MotionEvent.ACTION_DOWN时,用checkForLongClick方法检测长按逻辑
当MotionEvent.ACTION_UP时,调用了performClick()方法,内部调用了onClick()方法。
public boolean onTouchEvent(MotionEvent event) {
...
final int action = event.getAction();
// 检查各种 clickable
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
...
removeLongPressCallback(); // 移除长按
...
performClick(); // 检查单击
...
break;
case MotionEvent.ACTION_DOWN:
...
checkForLongClick(0); // 检测长按
...
break;
...
}
return true; // ◀︎表示事件被消费
}
return false;
}
2.ViewGroup的事件分发
ViewGroup事件分发机制从dispatchTouchEvent()开始
- 通过onInterceptTouchEvent判断自身是否需要拦截。如果是,则调用自身的onTouchEvent
- 自身不需要或者不确定,则通过for循环,遍历当前ViewGroup下所有的子View,找到当前被点击的View,调用它的dispatchTouchEvent方法,从而实现ViewGroup到View的传递
用伪代码看起来是这个样子
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = false; // 默认状态为没有消费过
if (!onInterceptTouchEvent(ev)) { // 如果没有拦截交给子View
result = child.dispatchTouchEvent(ev);
}
if (!result) { // 如果事件没有被消费,询问自身onTouchEvent
result = onTouchEvent(ev);
}
return result;
}
3.细节
所有的事件都应该被同一View消费。
- View只有消费了ACTION_DOWN事件,才能接收到后续的事件,并且会将后续所有事件传递过来,除非上层View进行了拦截
什么时候ACTION_CANCEL会触发
- 如果上层View拦截了当前View正在处理的事件,那么当前View会受到一个ACTION_CANCEL,表示后续事件不会再传递下来。一般ACTION_CANCEL和ACTION_UP都作为View一次事件处理的结束
参考: