阅读 72

Android事件分发

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()开始

  1. 通过onInterceptTouchEvent判断自身是否需要拦截。如果是,则调用自身的onTouchEvent
  2. 自身不需要或者不确定,则通过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一次事件处理的结束

参考:

gcsSLoop的事件分发机制详解

Carson_Ho的Android事件分发机制详解:史上最全面、最易懂

关注下面的标签,发现更多相似文章
评论