首先明确事件分发主要是指触摸事件,以及点击事件等,这些MotionEvent。安卓整体是事件驱动的,所以从Looper中获取触碰等事件,首先分发到的应该是Activity中。我们要研究的是View中的分发机制,所以要弄清楚怎么分发到View的。从《安卓开发艺术探索》中可以知道,安卓的分发通过Activity到Window里,由于Window实际上只有PhoneWindow的实现,所以是到PhoneWindow里,再由PhoneWindow进行dispatchTouchEvent(),传到了DecorView里,这是安卓的顶层布局,是一个FrameLayout。
1.1.1 缓存的顶部触摸目标:
触摸事件发生后,肯定是有承载对象的,用户触碰到的是什么。安卓由于是层级的布局,触碰到的可能不止一个控件,在ViewGroup的逻辑,其中最顶部的也就是第一个碰到的目标mFirstTouchTarget。安卓内在分发的过程中,不断获取新的触碰目标,直到发送到顶部的view,这部分没有做缓存,只有第一个碰到的目标进行了缓存。
如果没有第一个触碰的目标,即mFirstTouchTarget是null,那么就说明事件中途被拦截了,ViewGroup会默认拦截接下来的事件。在很多书中描述的是后者的表现,但是没有看到其中的业务逻辑。
1.1.2 View可以不允许ViewGroup拦截,disallowInterceptTouchEvent
通过标记位可以让ViewGroup不能拦截除了Action_Down以外的事件。Down事件ViewGroup可以感知,是因为要通过这个事件真正打通分发的流程。
1.1.3 事件的接收
接受触摸事件主要由两个点来衡量:子元素是否在播放动画(这是探索里说的,但是我没找到逻辑)、点击坐标是否在区域内
1.2.1 触摸优先于点击,自定义优于默认逻辑
onTouchListener设置后,onTouch()会屏蔽掉onTouchEvent(),也就是事件不会继续传递了。而onTouchEvent()中,onClick()也会被onTouch()屏蔽掉。
(1) dispatchTouchEvent()
(2) onInterceptTouchEvent()
(3) onTouchEvent()
滑动事件是分发的,外部View和内部View都可以收到。滑动冲突从业务逻辑看并不是冲突,而只是内部、外部需要对滑动事件进行各自的响应,而不能直接外部消耗掉。
(1)外部、内部不一致
(2)外部、内部一致;
(3)嵌套
(1)外部拦截法:父容器处理是否拦截
(2)内部拦截法:子View处理,处理不了交由父View处理。由于分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作
内部拦截法的真正原理_momo_ibeike的博客-CSDN博客_内部拦截法
这里介绍了cancel事件是因为mFirstTouchTarget不为空,且要父容器拦截事件,需要给mFirstTouchTarget发送cancel事件,然后父容器去处理,这个细节非常多的博客都没有注意到。
另外,这个博客虽然啰嗦,但比较重点的就是介绍了mFirstTouchTarget的处理逻辑,在Down事件发生的时候会置为空且重置FLAG_DISALLOW_INTERCEPT。因此,打破了这个逻辑:一个事件被消费,此事件序列则一定有此容器处理。这个逻辑实际就是由mFirstTouchTarget等管理的。对于容器来说,不考虑拦截的话,如果mFirstTouchTarget不为空,交由其处理,否则,自己处理。因而内部拦截的时候,ACTION_MOVE由于mFirstTouchTarget是空,则会交给自己处理。这也是内部拦截法,一个默认的规则。在Down事件来到时候,父容器不拦截,子容器处理;后续事件本该全有子容器处理,但是Move的时候可以通过requestDisallowInterceptTouchEvent(false),让父容器拦截生效。此时由于Down事件在子容器返回true,父容器的mFirstTouchTarget不为空,所以父容器不拦截的时候会交给子容器处理,拦截的话,就发送cancel给子容器,然后自己处理,over,哈,我这应该说的比大多数博客都清楚了吧。
(3)较高版本的安卓有自带的内部recyclerview