「这是我参与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是不会响应的
如图,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事件后,如果自己不需要那么交给父容器处理,同时通知父容器后续的事件可以拦截