一、事件分发简介
android核心机制,用户点击屏幕控件的时候,指尖触碰到控件,会有按下(down)、移动(move)、抬起(up)操作,这一系列操作简称为事件,看上去简单,实际上包含了复杂的事件分发逻辑。我们知道一个activity里面有viewgroup、还有view,viewgroup里面有各种view,view与view甚至还会重叠,那么系统是如何判断用户点击的是哪个控件呢?
二、事件分发原理
这里涉及到三个核心的方法:
事件分发函数:override fun dispatchTouchEvent(ev: MotionEvent?): Boolean
事件执行函数:override fun onTouchEvent(event: MotionEvent?): Boolean
事件拦截函数:override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean
其中在activity中,没有事件拦截函数,因为默认根布局的事件肯定要往下分发,不需要拦截,下面会讲到,activity的分发会分发到window,然后到viewgroup;
在view中,没有事件拦截函数,因为没有子view,不需要往下传递事件;
viewgroup没有事件执行函数。
用户点击屏幕整个流程
1.调用activity的dispatchTouchEvent(),查看源码,调用到了window.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
这个简单易理解的吧,window层或者更下的viewgroup或者view消费了,那么返回true,表明整体消费了,如果最终发现子view没有一个愿意消费的,那么直接自己来,自己做touchEvent。为什么我说了"window层或者更下的viewgroup或者view消费了",因为后面讲解源码的时候会发现,其实dispatchTouchEvent这个函数包含了ontouchevent和onintercepttouchevent,且在里面还会有对子view的各种遍历,只要子view消费了,就最终返回给自己所有父布局dispatchTouchEvent=true 表明消费。
window这里实际上就是PhoneWindow,查看源码,调用了mDecor,熟悉源码的知道实际上mDecor就是activtiy 的跟布局,是一个不能设置大小(默认全屏)的FrameLayout
fun superDispatchTouchEvent(event: MotionEvent?): Boolean {
return mDecor.superDispatchTouchEvent(event)
}
ok,实际上mDecor.superDispatchTouchEvent(event)这个代码调用到了viewgroup.dispatchTouchEvent了。
2.调用viewgroup.dispatchTouchEvent,二话不说,我们直接看源码,这里省略部分代码,主要看主要的逻辑,重点逻辑标记了“核心”tag的注释,其实逻辑很简单,先判断一下需不需要拦截onInterceptTouchEvent,如果不需要拦截,就对子view遍历循环,继续调用dispatchTouchEvent,如果递归下去发现子view没有了,那么走最终调用view.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
//...
boolean handled = false; //核心:是否消费的标记位
if (onFilterTouchEventForSecurity(ev)) {
//校验一下事件是否合理安全,可以忽略
//...
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//核心:判断事件是不是需要分发给子view,注意,只有down事件时候判断一次,后面不会二次判断,所以事件down的时候被拦截了,后续所有事件都不会分发给子view了
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
&& !isMouseEvent;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
//核心:如果事件没拦截也没有取消,开始对子view开始分发
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)