view的事件分发和冲突处理

155 阅读3分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

View的事件分发机制

整个分发机制一般分为两个过程:从上到下事件传递,从下到上事件响应。

事件传递

之前我们说过Activity与View的关系为:Acitivity -> phoneWindow -> decorView -> viewGroup 那么事件传递的过程也是如此 Activity -> phoneWindow -> decorView -> viewGroup
在事件传递过程中需要注意几个点:

  • DOWN事件会刷新事件序列
  • 父容器中重写onInterceptTouchEvent方法可以提前拦截并处理事件,拦截之后事件将不会传给子View

事件响应

事件响应的过程为:view -> viewGroup 在这个过程中涉及到具体的事件处理
响应的规则为如果view如果设置了onTouchListener,会先响应onTouch接着响应onTouchEvent(如果onTouch返回true将不会响应onTouchEvent) 如果onTouchEvent中设置了onClickListener 那么接着还会响应onClick
view响应完成后事件不会在往上传递
用伪代码表示如下

View.dispatchEvent{
if onTouchListener.onTouch(): return true; 
return onTouchEvent{... onClick()...; } }

在事件分发机制中有一个经典的问题:如果子view不在父容器的范围内,当给子view设置点击事件后,子view是不会响应的

image.png

如图,view因为一些原因没有落在viewGroup的范围内,此时点击view是不会有响应事件的,这是因为当点击view后事件首先会传递到viewGroup,viewGroup只会传递给它范围内的子view,不会传递给view,因此view不会响应事件

View的滑动冲突

view的滑动冲突其实就是系统不知道将事件传递给哪个view进行处理,由于每个应用场景的不同,因此具体将事件传递给哪个view就由具体的场景来定。 其处理方法一般有两种:

  • 外部拦截法
  • 内部拦截法

外部拦截法

外部拦截法是指事件的处理权交给父容器,当事件传递到父view时由父view裁决是否拦截并处理,也就是之前提到过的重写onInterceptTouchEvent方法

内部拦截法

内部拦截法父容器不会拦截任何事件,所有事件都会传给子元素,如果子元素不需要,那么交给父元素处理 这里要注意的是 父容器不能拦截DOWN事件,因为一旦拦截会刷新事件序列,事件将不会传递给子view

内部拦截法需要配合requestDisallowInterceptTouchEvent使用以防止父容器在未知的情况下将事件给拦截了。
因此内部拦截法需要做两个地方的修改:对于父容器当事件DOWN到来时应该放行,对于子view当收到down事件后应该通过requestDisallowInterceptTouchEvent让父view不要拦截后续事件,当收到move事件后,如果自己不需要那么交给父容器处理,同时通知父容器后续的事件可以拦截