调用dispatchTouchEvent方法分发事件
调用onInterceptTouchEvent拦截事件
调用onTouchEvent来处理事件
应用层
关键方法和成员变量:
dispatchTouchEvent( ) / handled (boolean类型,作为dispatchTouchEvent内部变量和返回结果)
onInterceptTouchEvent( )
dispatchTransformedTouchEvent( ) / mFirstTouchTarget
onTouchEvent( )
onTouchListener优先级比onTouchEvent要高,onTouchEvent内有onClick的逻辑,也可以说onTouchListener优先级比onClick要高
不管有没有拦截事件,如果没有消费DOWN事件,则mFirstTarget = null,则接下来的MOVE和UP,都会走intercepter=true(由于intercepted = true;导致不会进入mFirstTouchTarget赋值逻辑,即mFirstTouchTarget=null,走super.dispatchTouchEvent,即自身onTouchEvent逻辑)
逻辑,所以走的是DecorView的onTouchEvent( ),即Activity的dispatchTouchEvent返回false,继续走Activity的onTouchEvent( )
哪个view的onTouchEvent( )消费了事件,则接下来的MOVE和UP都会走到这个view
mFirstTouchTarget 有什么作用
非多点触控:mFirstTouchTarget链表退化成单个TouchTarget对象。
多点触控,目标相同:同样为单个TouchTarget对象,只是pointerIdBits保存多个pointerId信息。
多点触控,目标不同:mFirstTouchTarget成为链表。
1.为什么要把mFirstTouchTarget设计成链表
是用于记录多点触控情况下,多目标控件的派分逻辑。2.记录目标的TouchTarget的pointerIdBits又起到什么作用。
配合mFirstTouchTarget,使多点触控时,同个目标可以对多个触控点进行合理的处理逻辑。3.按下A,再按下A(多点触控),为什么释放后A的点击事件只会触发一次。
ACTION_DOWN被A消耗,ACTION_POINTER_DOWN也被A消耗,此时相当于A是2个触控点的目标元素。
当释放任意一个触控点时,对应的事件是ACTION_POINTER_UP而不是ACTION_UP,导致不产生点击事件。4.按下A,按下VG(空白区域),为什么先释放A,却无法触发A的点击事件,继续释放VG,又会触发A的点击事件。
这里属于ACTION_POINTER_DOWN事件
ACTION_POINTER_DOWN无法找到目标时视为ACTION_DOWN目标接收派分5.按下VG(空白区域),再点击A,B无响应。
这里属于ACTION_POINTER_DOWN事件
当点击VG空白位置时,由于不存在消耗ACTION_DOWN的子控件,导致mFirstTouchTarget为空。任何后续事件的派分,都会由于拦截标记intercepted = true而被拦截,包括多点触控ACTION_POINTER_DOWN事件。
FrameWork层
1.1 硬件与内核部分
当屏幕被触摸,Linux内核会将硬件产生的触摸事件包装为Event存到/dev/input/event[x]目录下。
这样做的目的是将输入事件封装为通用的Event,供后续处理。
1.2 SystemServer部分
当系统启动时,在SystemServer进程会启动一系列系统服务,如AMS,WMS等。
其中还有一个就是我们管理事件输入的InputManagerService。
在其内部,会启动一个读线程,也就是InputReader,它会从系统也就是/dev/input/目录拿到任务,并且分发给InputDispatcher线程,然后进行统一的事件分发调度。
1.3 跨进程通信传递给App
App中的Window与InputManagerService之间的通信实际上使用的InputChannel,InputChannel是一个pipe,底层实际是通过socket进行通信。
在Activity启动时会调用ViewRootImpl.setView()。在ViewRootImpl.setView()过程中,也会同时注册InputChannel
最终在SystemServer进程中,WindowManagerService根据当前的Window创建了SocketPair用于跨进程通信,同时并对App进程中传过来的InputChannel进行了注册。这之后,ViewRootImpl里的InputChannel就指向了正确的InputChannel, 作为Client端,其fd与SystemServer进程中Server端的fd组成SocketPair, 它们就可以双向通信了。
App进程的主线程就会监听socket客户端,当收到消息(输入事件)后,回调NativeInputEventReceiver.handleEvent()方法,最终会走到InputEventReceiver.dispachInputEvent方法。(终于拿到输入事件)
2.1 事件回传到ViewRootImpl
1 | //InputEventReceiver.java |
ViewRootImpl不仅负责界面的绘制,同时负责事件的传递。
2.2 第一次责任链分发
接下来走到doProcessInputEvents中,其中涉及到事件分发中的第一次责任链分发。
1 | void doProcessInputEvents() { |
1.QueuedInputEvent是一种输入事件,链表结构,遍历传递给InputStage。
2.InputStage是处理输入的责任链,在调用deliver时会遍历责任链传递事件。
2.3 组装责任链
回到ViewRootImpl.setView方法中。
1 | public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { |
其中ViewPostImeInputStage:视图输入处理阶段,比如按键、手指触摸等运动事件,我们熟知的view事件分发就发生在这个阶段。
事件到达应用端的主线程,会通过ViewRootImpl进行一系列InputStage来处理事件。这个阶段其实是对事件进行一些简单的分类处理,比如视图输入事件,输入法事件,导航面板事件等等。
我们的View触摸事件就发生在ViewPostImeInputStage阶段。ViewRootImpl中的mView就是DecorView。