一:什么是事件分发
所谓事件分发,就是将一次完整的点击所包含的点击事件传递到某个具体的View或ViewGroup,让该View或该ViewGroup处理它(消费它)。分发是从上往下依次传递的,其中可能经过的对象有最上层Activity,中间层ViewGroup,最下层View。例如在屏幕中有一个Button控件,点击它时该次点击事件就会从Activity传到Button所在的ViewGroup,最后传到该Button控件去处理它。即事件分发就是从上往下依次遍历,直到找到能够处理消费这次点击事件的View或ViewGroup。
二:分发事件类型
事件分发中的事件指的是点击事件(Touch事件),一次完整的点击包含四种事件:
(1)ACTION.DOWN:手指触摸到屏幕
(2)ACTION.MOVE:手指在屏幕中滑动
(3)ACTION.UP:手指离开屏幕
(4)ACTION.CANCEL:取消点击事件
当手指点击屏幕中的Button控件时,点击事件包含(1),(3);当手指滑动屏幕中的ListView控件时,点击事件包含(1),(2),(3);当手指点击屏幕上的某个控件并滑动到屏幕之外时,点击事件包含(1),(2),(4)。
一般情况下一次完整的点击都是从DOWN事件开始,UP事件结束,中间可能包含多个或没有MOVE事件。
三:事件分发涉及到的方法
事件分发涉及到的方法有3个:dispatchTouchEvent()(分发事件)、onInterceptTouchEvent()(拦截事件)、onTouchEvent()(处理事件)。
Activity涉及到的方法:dispatchTouchEvent(),onTouchEvent()。
ViewGroup涉及到的方法:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()。
View涉及到的方法:dispatchTouchEvent(),onTouchEvent()。
由上可知,onInterceptTouchEvent()方法是ViewGroup独有的,另外两个方法是三者共有的。
四:事件分发流程
1.Activity的dispatchTouchEvent()方法。
每一次事件分发,都是从Activity中的dispatchTouchEvent()方法开始的。看一下该方法的源码:
可以看到,Activity的dispatchTouchEvent()方法内有两个if语句和一个return onTouchEvent语句。第一个if语句不用管,直接看第二个if语句,在第二个if语句中调用了getWindow().superDispatchTouchEvent()方法,如果该方法返回True,则Activity的dispatchTouchEvent()方法返回True,如果该方法返回False,则会触发第二个return语句去执行onTouchEvent()方法。
(1)getWindow().superDispatchTouchEvent()方法是什么方法?
1)首先getWindow()获取到的是phoneWindow的实例,在phoneWindow实例中的superDispatchTouchEvent()方法调用的是phoneWindow的内部类DecorView的superDispatchTouchEvent()方法,
2)我们知道DecorView是一个ViewGroup,所以此处调用的是ViewGroup的dispatchTouchEvent()方法
综上所述,getWindow().superDispatchTouchEvent()方法最后调用的是ViewGroup的dispatchTouchEvent()方法,从而实现了事件从Activity的dispatchTouchEvent()向下传递到ViewGroup的dispatchTouchEvent()方法。
(2)返回值分析
1)如果getWindow().superDispatchTouchEvent()方法返回True,则Activity的dispatchTouchEvent()也返回True,代表点击事件顺利从Activity传递到ViewGroup,交给ViewGroup进行下一步分发,Activity的分发任务结束。
2)如果getWindow().superDispatchTouchEvent()方法返回False,则会执行Activity的onTouchEvent()方法,而无论该方法返回True还是False,此次事件分发都结束。
综上所述,在Activity的dispatchTouchEvent()方法中会默认调用ViewGroup的dispatchTouchEvent()方法,代表事件从Activity传递到了ViewGroup去进行下一步事件分发。
盗用一张图总结Activity的事件分发流程:
2.ViewGroup的dispatchTouchEvent()方法。
看一看该方法的源码,由于该方法代码太长,只看部分重要的地方:
代码中有一个intercepted变量表示是否拦截该次事件,之后调用了onInterceptTouchEvent()方法,如果该方法返回True,intercepted赋值为True,表示ACTION_DOWN事件被ViewGroup拦截了;如果该方法返回False,intercepted赋值为False,表示ACTION_DOWN事件不会被拦截,而是继续向下传递。onInterceptTouchEvent()方法默认返回False不拦截,不过可以重写该方法返回True去拦截事件。
总结:如果onInterceptTouchEvent()方法返回True,表示拦截ACTION_DOWN事件,事件停止往下传递,ViewGroup会调用它的父类View的dispatchTouchEvent()方法,在该方法里调用onTouch()—>onTouchEvent()—>onClick()方法去处理事件,并且之后的ACTION_MOVE和ACTION_UP事件都会交给该ViewGroup处理,不会再往下传递。如果onInterceptTouchEvent()方法返回False,则代表ViewGroup不处理该事件,而是向下传递事件。
继续往下看源码:
上面代码中有一个if语句,里面用到了intercepted变量,如果之前intercepted变量赋值为True,表示拦截该事件,就不会触发if语句里面的逻辑代码;如果之前interepted变量赋值为False,表示不拦截该事件,就会触发这段if语句逻辑代码。
这段代码主要的作用就是遍历ViewGroup的子View或子ViewGroup,判断此次点击事件触摸的区域是否属于它的子View或子ViewGroup的区域范围,如果属于子View区域,就调用子View的dispatchTouchEvent()方法,将事件从ViewGroup传递到View去处理;如果属于子ViewGroup区域,就继续遍历该子ViewGroup的子视图,直到找到能够处理事件的View。
如果遍历到最下面View仍然找不到能够处理该次点击事件的View,就依次回调上一层的ViewGroup的onTouchEvent()方法,直到回调到Activity的onTouchEvent()方法。
盗用一张图总结ViewGroup的事件分发流程:
3.View的dispatchTouchEvent()方法。
看一看View的该方法的源码:
上面代码中首先第一个圈有一个布尔变量result,默认赋值False。
之后第二个圈有一个if语句块,里面有3个条件:当调用了setOnTouchListener()方法时,第一个条件为True;当当前控件能够被点击(enable)时,第二个条件为True;当onTouch()方法返回True时,第三个条件为True。此时result变量赋值True,表示该点击事件成功被onTouch()方法消费。
如果onTouch()方法返回False,则第二个圈里面的if语句块不会触发,而是触发第三个圈里面的if语句块。在该if语句块会调用onTouchEvent()方法。由于result默认赋值为False,所以第二个if语句的!result条件为True,如果onTouchEvent()返回True,代表事件被onTouchEvent()方法成功消费,此时View的dispatchTouchEvent()方法返回True。如果onTouchEvent()方法返回False,表示View无法处理该事件,此时View的dispatchTouchEvent()方法返回False。
综上所述,在View的dispatchTouchEvent()方法中首先会调用onTouch()方法,如果onTouch()方法能够消费该事件,就会直接返回True,从而直接结束View的dispatchTouchEvent()方法,不再执行onTouchEvent()方法;如果onTouch()方法不能消费该事件,就会返回False,从而继续执行onTouchEvent()方法。如果onTouchEvent()能够消费该事件,就会返回True从而直接结束dispatchTouchEvent()方法。如果onTouchEvent()方法也不能消费该事件,就会返回默认的False从而回调到上一层ViewGroup的onTouchEvent()方法,直到回调到Activity的onTouchEvent()方法。
最后看一看View的onTouchEvent()方法什么时候才能返回True,源码如下:
可以看到,在第一个圈中有一个布尔变量clickable,只要CLICKABLE和LONG_CLICKABLE有一个为True,clickable变量就为True,从而执行第二个圈里面的逻辑,并且onTouchEvent()方法也会返回True表示成功消费。而只要设置了setOnClickListener()和setOnLongClickListener()方法,CLICKABLE和LONG_CLICKABLE变量就为True。
在第二个圈的代码里面通过switch分别对四种点击事件进行处理,在处理ACTION_UP点击事件时,会调用performClick()方法,该方法里又调用了setOnClickListener()里的onClick()方法。从而可以知道三个点击方法的执行顺序是:onTouch()>onTouchEvent()>onClick()。
盗用一张图总结View的事件分发流程:
最后盗用两张图总结上述事件分发流程: