Android事件分发机制、滑动冲突解决

这里所说的事件,是MotrionEvent

所谓点击事件的时间分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生以后,系统需要把这个事件传递给一个具体的View,而这个传递过程就是分发过程。


事件分发


在了解事件分发前,应该先了解一下Activity的视图层级,因为这里只涉及事件的分发,所以做了一个比较简单的示意图:
这里写图片描述

Window、Activity、DecorView以及ViewRoot之间的关系


首先需要明确一点的事,事件分发机制实际上应用了责任链模式

事件分发的大致流程如下图所示:

具体的说说明可以参考这篇文章:安卓自定义View进阶-事件分发机制原理


接下来只是整理一些笔记(参考《Android开发艺术探索》):


(1)相关方法是说明

dispatchTouchEvent
方法用来进行事件分发,如果事件能够传给当前View,此方法一定会被调用,返回结果受当前View的onTouchEvent与下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件

onInterceptTouchEvent
在dispatchTouchEvent的内部调用,用来判断是是否拦截某个事件,如果当前ViewGroup(View不存在该方法,且在ViewGroup中默认返回不拦截)拦截了某个事件,那么在用一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件

(同一事件序列是指从手指接触屏幕的那一个刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,
这个时间序列以DOWN事件开始,中间含有数量不定的MOVE事件,最终以UP事件结束)

onTouchEvent
在dispatchTouchEvent的内部调用,用来处理点击事件,返回结果表示是否消耗了当前事件如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件
onTouchEvent默认消耗事件,即返回true,除非它是不可点击的,clickable和longClickable同时为false,其中longClickeable都默认为false,clickable则分情况,Button的默认为true,TextView的为false。而View的enable属性不影响onTouchEvent的默认返回值,自只要clickable和longClickable有一个为true即可)

根据上述描述可以知道:

如果一个View拦截了一个事件序列的某一个事件,则该事件后的事件都会默认被拦截。

可以知道如果一个View不消耗(消耗即返回true,但不代表一定要根据该事件实现某种逻辑,可以就是空逻辑)一个事件序列中的某一事件,那么接下来的一连串事件也就无法接收到了,因为事件一般是按序列来的,不先消耗前一个事件,则无法继续后序事件的处理。


(2)事件分发的宏观流程

一个事件的大概流程如下:

Activity -> Window(由PhoneWindow实现) -> DecorView(底层容器)-> RootView(通过setConentView设置的)

PhoneWindow(附属于Activity的,控制顶级View的外观和行为策略)和DecorView只负责事件的分发,不存在拦截与处理事件的逻辑。如果事件最终没有被消耗,就会返回给Activity,由其onTouchEvent消耗。


(3)当一个View需要处理事件时,如果它设置了OnTouchListner,那么OnTouchListener中的onTouch方法会被回调。这时事件如何处理还需要看onTouch的返回值,如果返回false,则当前View的onTouchEvent方法会被调用,否则不会。给View设置的OnTounchListener优先级大于onTouchEvent。(OnTouchListenr是从外部定义处理TouchEvent的事件的逻辑,onTouchEvent的逻辑则是事先被定义好的)。在onTouchEvent中,如果当前有设置OnClickListener,那么它的onClick方法会被调用(前提是当前View是可点击的,且它收到了down和up事件),OnClickListener的优先级最低。


(4)正常情况下,一个事件序列只能被一个View拦截且消耗。因为一旦一个元素拦截了某些事件,那么同一个事件序列的后续事件都会交给它处理,且其onInterceptTouchEvent不会再被调用,从而不能在传递给其它View处理。但是可以通过特殊手段做到,如一个View将本该自己处理的事件通过onTouchEvent强行传递给其它View处理。


(5)如果View不消耗除ACTION_DOWN以外的事件,那么这个事件序列将会消失,此时父元素的onTouchEvent不会被调用(因为父元素认为将事件传递给了子元素去处理),但是当前View可以持续收到后续事件,最后消失的事件会回传给Activity处理。


(6)通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是DOWN事件除外。


(7)事件传递给子元素,需要先判断子元素是否能够接收到点击事件:子元素是否在播放动画和点击事件的坐标是否落在子元素的区域内。


滑动冲突

滑动冲突的场景可以简单分为以下三种:

  • 外部滑动方向和内部不一致
  • 外部滑动方向与内部一致
  • 上面两种情况的嵌套

解决方法

(1)外部拦截法
点击事件都先经过父容器的拦截处理,拦截住需要事件,防止传递到子元素。比较符合事件分发的机制,需要重写父容器的onInterceptTouchEvent方法,在内部做出相应拦截即可。

(2)内部拦截法
父容器不拦截任何事件,所有事件都交给子元素,如果子元素需要就直接消耗掉,否则交由父容器处理。需要配合requsestDisallowTouchEvent方法才能正常工作。除了子元素需要处理以外,父容器也要默认拦截除了DOWN意外的其他事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值