Android中对视图的Touch事件进行分发处理。
员工经过长期磨合与沉淀,具备了协作精神,得以通过团队的力量开发出优质的产品。成都创新互联坚持“专注、创新、易用”的产品理念,因为“专注所以专业、创新互联网站所以易用所以简单”。公司专注于为企业提供网站建设、成都做网站、微信公众号开发、电商网站开发,重庆小程序开发公司,软件按需网站策划等一站式互联网企业服务。
单手指操作:ACTION_DOWN - ACTION_MOVE - ACTION_UP
多手指操作:ACTION_DOWN - ACTION_POINTER_DOWN - ACTION_MOVE - ACTION_POINTER_UP - ACTION_UP.
(1) dispatchTouchEvent() :事件分发
(2) onInterceptTouchEvent() :事件拦截
(3) onTouchEvent() :事件处理
ViewGroup 的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。
View 的相关事件只有两个:dispatchTouchEvent、onTouchEvent。
先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:
点击事件达到顶级 View(一般是一个 ViewGroup),会调用 ViewGroup 的 dispatchTouchEvent 方法,如果顶级 ViewGroup 拦截事件即 onInterceptTouchEvent 返回 true,则事件由 ViewGroup 处理,这时如果 ViewGroup 的 mOnTouchListener 被设置,则 onTouch 会被调用,否则 onTouchEvent 会被调用。也就是说如果都提供的话,onTouch 会屏蔽掉 onTouchEvent。在 onTouchEvent 中,如果设置了 mOnClickListenser,则 onClick 会被调用。如果顶级 ViewGroup 不拦截事件,则事件会传递给它所在的点击事件链上的子 View,这时子 View 的 dispatchTouchEvent 会被调用。如此循环。
Android 事件机制包含系统启动流程、输入管理(InputManager)、系统服务和 UI 的通信(WindowManagerService + ViewRootImpl + Window)、事件分发等一系列的环节。
Android 系统中将输入事件定义为 InputEvent,根据输入事件的类型又分为了 KeyEvent(键盘事件) 和 MotionEvent(屏幕触摸事件)。这些事件统一由系统输入管理器 InputManager 进行分发。
在系统启动的时候,SystemServer 会启动 WindowManagerService,WMS 在启动的时候通过 InputManager 来负责监控键盘消息。
InputManager 负责从硬件接收输入事件,并将事件通过 ViewRootImpl 分发给当前激活的窗口处理,进而分发给 View。
Window 和 InputManagerService 之间通过 InputChannel 来通信,底层通过 socket 进行通信。
Android Touch 事件的基础知识:
KeyEvent 对应了键盘的输入事件;MotionEvent 就是手势事件,鼠标、笔、手指、轨迹球等相关输入设备的事件都属于 MotionEvent。
InputEvent 统一由 InputManager 进行分发,负责与硬件通信并接收输入事件。
system_server 进程启动时会创建 InputManagerService 服务。
system_server 进程启动时同时会启动 WMS,WMS 在启动的时候就会通过 IMS 启动 InputManager 来监控键盘消息。
App 端与服务端建立了双向通信之后,InputManager 就能够将产生的输入事件从底层硬件分发过来,Android 提供了 InputEventReceiver 类,以接收分发这些消息:
Window 和 IMS 之间通过 InputChannel 通信。InputChannel 是一个 pipe,底层通过 socket 进行通信。在 ViewRootImpl.setView() 过程中注册 InputChannel。
Android 事件传递机制是 先分发再处理 ,先由外部的 View 接收,然后依次传递给其内层的 View,再从最内层 View 反向依次向外层传递。
三个方法的关系如下:
分发事件:
应用了树的 深度优先搜索算法 (Depth-First-Search,简称 DFS 算法),每个 ViewGroup 都持有一个 mFirstTouchTarget, 当接收到 ACTION_DOWN 时,通过递归遍历找到 View 树中真正对事件进行消费的 Child,并保存在 mFirstTouchTarget 属性中,依此类推组成一个完整的分发链。在这之后,当接收到同一事件序列的其它事件如 ACTION_MOVE、ACTION_UP 时,则会跳过递归流程,将事件直接分发给下一级的 Child。
ViewGroup 分发事件的主要的任务是找一个 Target,并且用这个 Target 处理事件,主要逻辑如下 :
为什么倒序查找 TouchTarget?
如果按添加顺序遍历,当 View 重叠时(FrameLayout),先添加的 View 总是能消费事件,而后添加的 View 不可能获取到事件。
拦截事件:
[1] Android 事件分发机制的设计与实现
[2] Android 事件拦截机制的设计与实现
DecorView →PhoneWindow →Activity→ViewGroup→view
下面我们根据按键事件的分发流程,抽丝剥茧,逐一分析。
private int processKeyEvent(QueuedInputEvent q)
1、DecorView.java
2、Activity.java
3、ViewGroup.java
4、View.java
通过该方法,接收器receiver的onKeyDown、onKeyUp、onKeyLongPress、onKeyMultiple等方法将被回调。
在上述按键事件的入口中提到的ViewRootImpl中
如果mView.dispatchKeyEvent(event)返回true,则结束事件分发;
如果返回false,则调用如下方法
继续执行后续的焦点导航流程。
焦点导航的总体流程就是:
1、View focused = mView.findFocus();//从视图树的顶层,即DecorView一层一层的递归查找当前获得焦点的view
2、View v = focused.focusSearch(direction);根据导航的方向查找下一个可获取焦点的view
3、v.requestFocus(direction, mTempRect)请求获取焦点
4、v.requestFocus(direction,mTempRect)内部,调用mParent.requestChildFocus(this, focused)逐层递归向上级通知
ViewRootImpl.java
mView即DecorView,从DecorView开始,一层一层的向下递归查找当前获得焦点的view
找到了当前获得焦点的focused,调用该焦点view的focusSearch(direction)方法查找direction方向上下一个将要获取焦点的view。
focused.focusSearch(direction)实际上会调用mParent.focusSearch(this, direction)方法,层层递归,直到调用到DecorView的focusSearch(this, direction)方法。
而DecorView继承ViewGroup,实际上最终会调用到FocusFinder.getInstance().findNextFocus(this, focused, direction),this 就是DecorView对象。
最终会调用到DecorView父类ViewGroup中的FocusFinder.getInstance().findNextFocus(this, focused, direction);
ViewGroup.java
FocusFinder.java
搜索到下一个获取焦点的view后,调用该view.requestFocus(direction, mTempRect)方法
注意:调用requestFocus(direction, mTempRect)需要区分调用者。
如果是ViewGroup,则会更加焦点获取策略,实现父View和子View之间获取焦点的优先级。
如下是ViewGroup.java 和View.java 中requestFocus方法是实现:
ViewGroup.java
View.java
View获取到焦点后,会调用mParent.requestChildFocus(this, focused)逐层递归向上级通知
ViewGroup.java