关于Android事件分发的设计模式理解与思考
关于Android事件分发的设计模式理解与思考
在现在Android智能机上,触碰几乎成为了唯一的交互方式。那么触碰消息在Android系统当中怎么进行分发的呢?
在事件分发处理上,Android使用了责任链模式。将message放到消息链中让其自行匹配处理。
事件分发机制
事件分发机制大概可以分为三个部分:
- 事件生产: 由客户操作屏幕产生
- 事件分发: 用户和设备交互产生事件后,事件层层传递,最后交由目标view的这个过程就是事件分发
- 事件消耗: 目标view消耗了事件,并做出反应就是事件消耗
MotionEvent
MotionEvent 的事件有哪些,他们的定义是什么?
- ACTION_DOWN: 手指首次触摸到屏幕时触发
- ACTION_UP: 手指离开屏幕时触发
- ACTION_CANCEL: 事件被上层拦截时触发
- ACTION_MOVE: 手指在屏幕上滑动时触发,会多次触发
事件分发处理有3个关键方法,我们通过这3个方法对message进行拦截、下发、传递处理等;
- dispatchTouchEvent: (事件下发--view和viewGroup都有的方法)
- onInterceptTouchEvent: (拦截下发的事件,并交给自己的onTouchEvent处理--viewgroup才有的方法)
- 同一个事件组只会调用一次该方法
- 因为只有事件是ACTION_DOWN时才会判断该方法
- onTouchEvent: (事件上报--view和viewGroup都有的方法)
上面三个方法如果返回值为true,则代表终止传递
事件传递流程
在一个activity中有两个viewGroup、一个view。
Acitvity ->PhoneWindow-->DockerView>ViewGroup ->View
- 查看Acitvity源码可以看到,Acitvity实现了Window类的一个回调接口
Window.Callback
。
因此Acitvity中有dispatchTouchEvent
和onTouchEvent
两个方法,当一个事件被产生后,首先Activity的dispatchTouchEvent
会被执行,这是如果在Acitvity重写了该方法,事件将交由Acitvity的onTouchEvent
处理 - 否则将继续传递到PhoneWindow。在Acitvity的
dispatchTouchEvent
这个方法中可以看到,它执行了getWindow().superDispatchTouchEvent
,这个windows就是PhoneWindow - 继续往下看,能发现事件传递到了DecorView,而DecorView的父类是FrameLayout
- 接下来事件将逐级传递,最终到view
- 如果这个过程
dispatchTouchEvent
始终返回true,事件一直没有被消耗。那么事件将在onTouchEvent
这个方法回传
上图:
- 那么,假如activity的dispatchTouchEvent返回true呢,那么消息将终止传递。
- 当事件走到viewGroup1时,将调用 onInterceptTouchEvent查询是否拦截事件,默认为false不拦截,如果为true将停止向下传递,将自己处理该事件,后续事件将都交于该事件处理。在同一个事件组, onInterceptTouchEvent只调用一次。
- 当事件走到view时(注意,view没有 onInterceptTouchEvent方法,viewGroup才有 )将调用onTouchEvent,如果该方法返回true,事件终止传递,事件被消费,将逐层返回true结果。如果返回false,将事件传递给上层onTouchEvent处理事件。
事件冲突
什么是事件冲突?
当一个事件,对应有多个人想要去处理它?而如果处理的对象不是我们想要的对象,就叫发生了冲突。
例子1:
同时添加setOnClickListener()
和setOnTouchListener()
两个监听事件,并在onClick()
和onTouch()
中打印日志。
onTouch()==false
的情况下onClick()
和onTouch()
都会打印日志,当onTouch()==true
时,onClick()
不会打印日志。
这是什么原因,查看view的源码可以看到,在view的dispatchTouchEvent()
这个方法中,有一个判断,当onTouch()==true
时,dispatchTouchEvent()
将返回true,而通过事件分发可以知道,dispatchTouchEvent()
返回true表示这个事件已经被该view消耗了,代表事件终止传递。
例子2:
有一个垂直滑动的view嵌套了一个水平滑动的view。这种情况存在上下滑动和水平滑动,两者会发生冲突。
解决思路1:
- 在父view的
onInterceptTouchEvent
中拦截事件,当事件为ACTION_DOWN时不拦截。 - 在子view中的
dispatchTouchEvent
方法中调用requestDisallowInterceptTouchEvent(true)
方法来禁止父view拦截事件。当在合适的情况再调用requestDisallowInterceptTouchEvent(false)
允许父view拦截事件。
比如:
dispatchTouchEvent
中,事件类型为ACTION_DOWN时,禁止父view拦截事件,然后判断滑动的方向是垂直还是水平,如果是垂直则调用requestDisallowInterceptTouchEvent(false)
允许父view处理事件。
解决思路2:
- 在父view的
onInterceptTouchEvent
方法中处理拦截事件,判断滑动方向。如果是垂直则拦截事件,自己处理。如果是水平方向则不拦截,让子view处理。这样就使父view的垂直滑动和子view的水平滑动都能流程进行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)