- 本文分析从Activity是如何向下分发的,暂不分析事件从InputManagerService如何传递到Activity
- 下列均属于个人理解 若有不正确欢迎指正
- 下面分析基于Android11 源码
事件分发中的事件指的是什么
- 点击事件(Touch事件)即用户点击屏幕所产生的事件
- 该事件会被封装成MotionEvent对象
事件有哪几种类型
事件类型 | 具体触发动作 | 对应值 |
---|---|---|
MotionEvent.ACTION_DOWN | 用户按下发生动作 | 0 |
MotionEvent.ACTION_UP | 用户抬起时 | 1 |
MotionEvent.ACTION_MOVE | 用户滑动屏幕时 | 2 |
MotionEvent.ACTION_CANCEL | 事件被中止取消 | 3 |
事件流指的是什么
- 上述事件
down(按下)
move(滑动)
up(抬起)
cancel(中止/取消)
组成了事件流 - 事件从
按下
开始 中间伴随着N次``滑动
事件,以抬起,中止/取消
结束
事件分发(传递)又是指什么
- 将点击事件传递到具体处理事件的view的过程
事件从哪里开始分发的呢
- 当用户点击屏幕时 由
InputManagerService
收集事件并交由WindowManagerService
分发到Activity
处理的 - 当然我们不在这里分析这个过程主要分析到了Activity之后是如何分发的
1事件分发
1.1分发事件的组件
- 与Activity 界面构成有关
Activity
由Activity
,PhoneWindow
,DecorView
,ViewGroup
,View
组成所以事件应在其中分发 - 但是
PhoneWindow
调用的是DecorView
的superDispatchTouchEvent
也就是调用的ViewGroup
的DispatchTouchEvent
- 所以我们一般说的事件分发者 为
Activity
,ViewGroup
,View
基于分发也从这三个组件分析 - 下图为事件传递方法调用时序图
1.2 分发涉及到的关键方法
- 由上图可知 关键方法
Activity | ViewGroup | View | |
---|---|---|---|
dispatchTouchEvent | 有 | 有 | 有 |
onInterceptTouchEvent | 没有 | 有 | 没有 |
onTouchEvent | 有 | 没有 | 有 |
-
dispatchTouchEvent
- 从上面的图种可以看出这是唯一 一个三个组件都存在的方法
- 该方法用来分发事件
- 若组件该方法返回
true
则表示事件已经被消费
事件终止
停止分发
- 这里的消费
若该组件存在子View
则事件可能被子View,或自身消费
,若不存在子View
则被自身消费 - 若组件该方法返回
false
则事件,表示该组件没有 消费/处理
事件,并将事件处理结果(此时事件处理结果为false)
返回给该组件的父级(即调用该组件dispatchTouchEvent方法的组件 )
由父级看自身是否处理事件,若不处理仍返回false,并再次向父级的父级传递,若一直不处理则一直传递到Activity这就是事件处理规则
- 综上所述可得结论
- 事件传递是由最外层到最内层即
Activity-ViewGroup-View
- 事件处理是从内层开始并逐级返回到外层 若中间不产生事件拦截则从最内层VIew开始处理若产生拦截则从拦截View开始
- 事件传递是由最外层到最内层即
- 若组件该方法返回
-
onInterceptTouchEvent
- 该方法只存在于ViewGroup中 用来拦截事件
- 该方法返回
true
表示事件被拦截
要看自身是否处理事件 - 该方法返回
false
表示事件不拦截
,继续分发
-
onTouchEvent
- 对于这个只存在于Activity和View中的方法是用来处理事件的
- 该方法返回
true
表示事件被消耗
- 该方法返回
false
表示事件未消耗
-
下面我们看具体的源码来分析
2 事件在Activity中的分发
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// ->> 1 此处一般用于做屏保
onUserInteraction();
}
// ->> 2 若getWindow().superDispatchTouchEvent(ev)返回true 则直接返回true
// 说明事件被处理,事件停止分发 否则返回false 继续向下调用 onTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//->> 3 若getWindow().superDispatchTouchEvent(ev)返回false 则执行onTouchEvent
return onTouchEvent(ev);
}
- 从上面代码中可以看出,当事件传递到了Activity后首先调用了
getWindow().superDispatchTouchEvent(ev)
该方法最终调用的是ViewGroup
的dispatchTouchEvent
- 即 事件先由Activity传递到
ViewGroup
的dispatchTouchEvent
- 若
ViewGroup
的dispatchTouchEvent
返回True
- 则事件已经被
ViewGroup
或其子View
消费Activity
的dispatchTouchEvent
直接返回true 即事件消费停止分发
- 则事件已经被
- 若
ViewGroup
的dispatchTouchEvent
返回False
- 则事件没有被消费 继续调用
Activity
的onTouchEvent
onTouchEvent
返回true
事件已消费
则Activity
的dispatchTouchEvent
返回True
事件消费让调用Activity
的dispatchTouchEvent
的组件知道该事件已经消费onTouchEvent
返回False
事件未消费
则Activity
的dispatchTouchEvent
返回False
事件未消费,让调用Activity
的dispatchTouchEvent
的组件知道该事件未消费,看否继续处理
- 则事件没有被消费 继续调用
- 若
3 ViewGroup中的事件分发
- 上面说到 事件到了Activity 后交给ViewGroup的
dispatchTouchEvent
处理 下面看着部分源码
public boolean dispatchTouchEvent(MotionEvent ev) {
//......
boolean handled = false;
final boolean intercepted;
//....
//->> 1 得到 intercepted的值
intercepted = onInterceptTouchEvent(ev);
//....
//->> 2 检测是否拦截
if (!canceled && !intercepted) {
//....
//->> 3 遍历子View
for (int i = childrenCount - 1; i >= 0; i--) {
//...
// ->> 4 将事件分发至子view看子是否处理事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)){
//...
//->> 5 mFirstTouchTarget 赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//->> 6 若有事件处理则跳出
break;
}
}
}
//....
//->> 7 看自身是处理事件
if (mFirstTouchTarget == null) {
//->> 8 向下分发事件 此时child为null 看自身是否处理事件
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}else {
//->> 9
handled = true;
}
//....
return handled;
}
// 事件向下分发 看是否自身处理事件 child==null 表示自身处理 child!=null 表示child处理
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//--> 9 调用super的dispatchTouchEvent 也就是View的dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
//->> 10 调用child的dispatchTouchEvent 若child是View 则调用View的dispatchTouchEvent
//若child是viewGroup 则调用ViewGroup的dispatchTouchEvent
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
return handled;
}
- 若
1
处代码onInterceptTouchEvent
返回true 拦截事件
则2
处检测为拦截if
判断不会进入则不会执行3,4,5, 6
即(不会执行遍历 子view ,询问子view 是否处理事件的过程)
- 此时直接到了
7
判断mFirstTouchTarget
毫无疑问 遍历过程都没执行(3,4,5,6)
所以该值一定为null此时执行8
调用dispatchTransformedTouchEvent
询问自身是否处理事件 其中参数child=null
- 接着执行
9
调用super.dispatchTouchEvent(event)
ViewGroup继承自View
即调用的是View的dispatchTouchEvent
- 若该方法返回true 则表示事件已经消费 此时
Activity的dispatchTouchEvent返回true 事件消费
- 若该方法返回false 则表示事件已经未消费 此时
Activity执行onTouchEvent
- 若该方法返回true 则表示事件已经消费 此时
- 此时直接到了
- 若
1
处代码返回的是false
表示不拦截- 则
2
处代码进入if判断 执行3 遍历子view
- 执行
4 调用dispatchTransformedTouchEvent 询问子view是否处理事件
此时child ! = null
, - 此时执行
10
调用child.dispatchTouchEvent
- 若
child
是ViewGroup
则调用ViewGroup
的dispatchTouchEvent
所以这里有个递归
一直递归到最内层View
到最内层View的时候处理步骤就于child是View的处理步骤相同了 - 若
child
是View
则调用View
的dispatchTouchEvent
- 若返回true 则表示事件被消费 执行
5 mFirstTouchTarget 赋值
- 执行
6 break 跳出循环
- 接着执行
7 判断 mFirstTouchTarget
此时该值不为 null 执行9 handled = true;
执行return
该方法返回true
事件消费,此时Activity的dispatchTouchEvent返回true 事件消费
- 执行
- 若返回
false
- 则不执行
5 mFirstTouchTarget 赋值
6 break 跳出循环
- 此时直接到了
7
判断mFirstTouchTarget
遍历过程虽然执行但是赋值 所以该值为null此时执行8
调用dispatchTransformedTouchEvent
询问自身是否处理事件 其中参数child=null
- 接着执行
9
调用super.dispatchTouchEvent(event)
ViewGroup继承自View
即调用的是View的dispatchTouchEvent
- 若该方法返回true 则表示事件已经消费 此时
Activity的dispatchTouchEvent返回true 事件消费
- 若该方法返回false 则表示事件已经未消费 此时
Activity执行onTouchEvent
- 从步骤
7
开始 这里与onInterceptTouchEvent
返回true 拦截事件
中处理步骤就相同了
- 若该方法返回true 则表示事件已经消费 此时
- 则不执行
- 若返回true 则表示事件被消费 执行
- 若
- 则
4 View中的事件处理
- 根据上述
VIewGroup
的事件分发最终是触发了View
的dispatchTouchEvent
看下其中部分源码 - 有意思的来了 这里为什么说的是事件处理 没说分发这个后面会说到
- 上源码
public boolean dispatchTouchEvent(MotionEvent event) {
//...
if (onFilterTouchEventForSecurity(event)) {
//...
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
//->> 1 看onTouchlistener是否未空,是否处理 返回true 处理 直接执行最后return
// 返回false 未处理执行 2 onTouchEvent
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//->> 2
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
public boolean onTouchEvent(MotionEvent event) {
//....
//->> 3 判断是否可点击 若不可点击 直接返回 false 可点击则执行4 处理clickListener
// 设置则判断是否up 事件 是则处理click
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//->> 4 up事件这里处理了ClickListener
performClickInternal();
break;
}
return true;
}
return false;
}
private boolean performClickInternal() {
return performClick();
}
public boolean performClick() {
// ->> 5 点击事件不为空则返回true 否则返回false
if (li != null && li.mOnClickListener != null) {
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
- 根据上述代码 事件传递到view中后我们意外的发现view中只有处理事件的代码并没有继续向下分发
- 那我们看下事件是如何处理
- 首先在代码
1
看onTouchlistener
是否未null
若为null 则向下执行2 onTouchEvent
若不为null
看TouchListener
是否处理事件- 若处理则返回
true
,标记事件已经消费 不在向下执行,此时ViewGroup的dispatchTouchEvent
返回true
Activity的dispatchTouchEvent返回true
- 若不处理则返回
false
事件未消费则向下执行2 onTouchEvent
此时看代码3 判断view是否可点击
- 若可点击返回
true
则表示事件处理并执行代码4,5
去处理ClickListener
,此时ViewGroup的dispatchTouchEvent
返回true
Activity的dispatchTouchEvent返回true
- 若不可点击直接返回false 此时
ViewGroup的dispatchTouchEvent
返回false
Activity执行onTouchEvent
- 若可点击返回
- 若处理则返回
- 接上面问题 我们发现View中其并没有做事件分发只是做了事件处理,ViewGroup则重写了View的dispatchTouchEvent 增加了一个事件拦截,和将事件传递到子View
- 所以我们理解是 VIewGroup存在事件分发 view 是处理事件
5 总结
- 根据上述图个人理解
+ 事件传递是由最外层到最内层即Activity-ViewGroup-View
+ 事件处理是从内层开始并逐级返回到外层 若中间不产生事件拦截则从最内层VIew开始处理若产生拦截则从拦截View开始
+ 可以理解成事件分发从外到内事件处理从内到外。。。