前言
视图系统主要包括 排版渲染 和 事件分发 这两大类工作。
其中 客户端 运行在用户进程中,负责 可视化内容的排版 和 向服务端输出,以及 接收来自服务端的事件并在视图树中分发;
而 视图服务 运行在系统进程中,负责 对排版内容的渲染,以及 传递触控事件给客户端。
android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件。
一次屏幕触摸会发生什么?
触发了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->ACTION_MOVE->ACTION_MOVE…->ACTION_MOVE->ACTION_UP。到底有多少个MOVE呢?这取决于你点击屏幕时手接触多久屏幕。
当屏幕中包含一个ViewGroup,而这个ViewGroup又包含一个子view,这个时候android系统如何处理Touch事件呢?到底是ViewGroup来处理Touch事件,还是子view来处理Touch事件呢?onTouchEvent和OnClickListener是如何执行的?先留几个疑问
解决疑问前,先要理清楚ViewGroup这个类中,和TouchEvent处理密切相关的3个方法:
- public boolean dispatchTouchEvent(MotionEvent ev) 这个方法用来分发TouchEvent
- public boolean onInterceptTouchEvent(MotionEvent ev) 这个方法用来拦截TouchEvent(View类中没有这个方法)
- public boolean onTouchEvent(MotionEvent ev) 这个方法用来处理TouchEvent
接下来我们来理一下逻辑
一次没有人工干预的事件是这样的:
当TouchEvent发生时,首先交互界面(Activity或者Fragment)最顶层的ViewGroup先获取到。TouchEvent最先到达最顶层 viewGroup的 dispatchTouchEvent 进行事件分发,此时dispatchTouchEvent 调用父类的同名方法super.dispatchTouchEvent(event)。接着到达 onInterceptTouchEvent,也是调用父类同名方法super,默认是不拦截,继续传递到下一个View或者ViewGroup。如果传到了View,先到达dispatchTouchEvent,这时候不往下传了,因为View的事件分发super方法和ViewGroup的不一样,他会直接交给自己的onTouchEvent处理。
有人工干预的情况,也就是改写了上述出现的3个方法,手动返回true或者false而不是调用super方法。
总结
- dispatchTouchEvent方法
- View和ViewGroup不同在于当调用super时,ViewGroup会进行分发,而View会交给他的onTouchEvent处理。
- View和ViewGroup返回true、返回false的处理逻辑都是一样的。返回true则消费,返回false则回传给父View的onTouchEvent处理。注意:返回false后,此时后面的事件都接收不到了,因为会往上传,最后由哪个View处理,以后的所有事件都交由它来处理(怎么理解这句话呢,好比我有A、B2个ViewGroup,C是View,我重写C里的dispatchTouchEvent方法,让他返回false,那么点击一次屏幕,AB的分发和拦截方法被调用,到C这分发返回false,直接往上传:→B(onTouchEvent)→A(onTouchEvent)→窗口层级(onTouchEvent),此时在ABC里MOVE和UP的事件打印见不到了,因为后面的MOVE或者UP的事件都交给了窗口层级处理了,不信你可以在Activity层级的onTouchEvent和dispatchTouchEvent打印一下是有这2个事件的;如果你不想让DecorView这样的窗口层级处理,只需要在A中重写onTouchEvent,返回true,那么虽然BC后面的事件收不到,但是在A里面有MOVE和UP的打印)。
- onInterceptTouchEvent方法
- 因为只有ViewGroup有,所以默认的super方法和返回false都是表示不拦截,返回true则交给自己的onTouchEvent处理。
- onTouchEvent方法
- View和ViewGroup返回true、返回false的处理逻辑都是一样的。不同的地方在于super方法,View是直接消费,ViewGroup则是回传给父View的onTouchEvent处理,一直往上传直到传到某个ViewGroup返回true消费掉。值得注意的是,事件触发是先触发onTouch,再触发onClick,如果onTouch方法返回tue,表示消费掉该事件,不在继续进行事件传递,onClick也不会被调用。
补充:View的onTouch和onTouchEvent联系
①如果onTouch()方法返回值是true(事件被消费)时,则onTouchEvent()方法将不会被执行;
②只有当onTouch()方法返回值是false(事件未被消费,向下传递)时,onTouchEvent方法才被执行。
③平时我们使用的OnClickListener,其优先级最低,即处于事件传递的尾端。(实现都基于onTouchEvent)
④给View设置监听OnTouchListener,重写onTouch()方法。其优先级比onTouchEvent()高。如果不返回false,那么设置的点击监听等也就意味着失效了。
看一道小米的面试题
问题解答
ACTION_DOWN在3个界面传递;ACTION_MOVE在A、B界面传递,B拦截了并调用onTouchEvent处理了。ACTION_UP在A、B界面传递。
问题分析
ACTION_DOWN没有限制;ACTION_MOVE到B的时候由于onInterceptTouchEvent拦截了,直接被B的onTouchEvent处理,onInterceptTouchEvent返回true之后,看Log知道,之后onInterceptTouchEvent都不会调用了,后面的ACTION_MOVE和ACTION_UP事件都不会往下传了,都是在B的onTouchEvent处理。具体可以看下面的日志。
打印日志
A
12-06 10:52:41.980 20891-20891/com.phz.testtouchevent E/ALayout: dispatchTouchEvent:ACTION_DOWN
12-06 10:52:41.980 20891-20891/com.phz.testtouchevent E/ALayout: onInterceptTouchEvent:ACTION_DOWN
12-06 10:52:42.006 20891-20891/com.phz.testtouchevent E/ALayout: dispatchTouchEvent:ACTION_MOVE
12-06 10:52:42.006 20891-20891/com.phz.testtouchevent E/ALayout: onInterceptTouchEvent:ACTION_MOVE
12-06 10:52:42.023 20891-20891/com.phz.testtouchevent E/ALayout: dispatchTouchEvent:ACTION_MOVE
12-06 10:52:42.023 20891-20891/com.phz.testtouchevent E/ALayout: onInterceptTouchEvent:ACTION_MOVE
12-06 10:52:42.029 20891-20891/com.phz.testtouchevent E/ALayout: dispatchTouchEvent:ACTION_MOVE
12-06 10:52:42.029 20891-20891/com.phz.testtouchevent E/ALayout: onInterceptTouchEvent:ACTION_MOVE
12-06 10:52:42.029 20891-20891/com.phz.testtouchevent E/ALayout: dispatchTouchEvent:ACTION_UP
12-06 10:52:42.029 20891-20891/com.phz.testtouchevent E/ALayout: onInterceptTouchEvent:ACTION_UP
B
12-06 10:52:41.980 20891-20891/com.phz.testtouchevent E/BLayout: dispatchTouchEvent:ACTION_DOWN
12-06 10:52:41.980 20891-20891/com.phz.testtouchevent E/BLayout: onInterceptTouchEvent:ACTION_DOWN
12-06 10:52:42.006 20891-20891/com.phz.testtouchevent E/BLayout: dispatchTouchEvent:ACTION_MOVE
12-06 10:52:42.006 20891-20891/com.phz.testtouchevent E/BLayout: onInterceptTouchEvent:ACTION_MOVE
12-06 10:52:42.023 20891-20891/com.phz.testtouchevent E/BLayout: dispatchTouchEvent:ACTION_MOVE
12-06 10:52:42.023 20891-20891/com.phz.testtouchevent E/BLayout: onTouchEvent:ACTION_MOVE
12-06 10:52:42.029 20891-20891/com.phz.testtouchevent E/BLayout: dispatchTouchEvent:ACTION_MOVE
12-06 10:52:42.029 20891-20891/com.phz.testtouchevent E/BLayout: onTouchEvent:ACTION_MOVE
12-06 10:52:42.029 20891-20891/com.phz.testtouchevent E/BLayout: dispatchTouchEvent:ACTION_UP
12-06 10:52:42.029 20891-20891/com.phz.testtouchevent E/BLayout: onTouchEvent:ACTION_UP
C
12-06 10:52:41.980 20891-20891/com.phz.testtouchevent E/CLayout: dispatchTouchEvent:ACTION_DOWN
12-06 10:52:41.980 20891-20891/com.phz.testtouchevent E/CLayout: onTouchEvent:ACTION_DOWN