基础
在分析事件分发之前,我们先来了解三个相关的重要知识点:
- 事件分发的对象是什么?
- 谁在分发?
- 依赖于什么分发?
我们先把这三个问题弄清楚了再来看具体的原理:
-
事件分发的对象就是我们的触摸屏幕产生的
Touch
事件。最常见的事件类型为:down、up、move和cancel。-
MotionEvent.ACTION_DOWN
:触摸到屏幕所立即产生的事件类型 -
MotionEvent.ACTION_UP
:离开屏幕所产生的事件类型 -
MotionEvent.ACTION_MOVE
:在屏幕上移动所产生的事件类型 -
MotionEvent.ACTION_CANCEL
:事件被取消,非人为的取消,比如:关机、锁屏
-
-
事件分发是在
Activity
、ViewGroup
和View
三者之间传递的过程,事件先传递到Activity
,接着传递到ViewGroup
,最后传给了View
。如下图 ① -> ② -> ③:
-
事件分发主要就是通过三个方法在上述三者之间传递:
-
dispatchTouchEvent(ev: MotionEvent?): Boolean
:分发、派发事件的方法 -
onInterceptTouchEvent(ev: MotionEvent?): Boolean
:拦截事件的方法 -
onTouchEvent(event: MotionEvent?): Boolean
:消费事件的方法
通过下方的表格来认识一下三个方法和三者之间的拥有关系:
dispatchTouchEvent() onInterceptTouchEvent() onTouchEvent() Activity 可以分发 无拦截方法 可消费 ViewGroup 可以分发 有拦截方法 可消费 View 可以分发 无拦截方法 可消费 理解了上面三个问题之后,我们就可以放心大胆的去通过源码去了解事件分发的原理了。
-
入口 Activity
首先看看 Activity
的 dispatchTouchEvent()
方法:
/**
* 事件分发的入口,可以override
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 如果事件类型为down,那么先执行一遍onUserInteraction()
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// getWindow()是获取Window对象,但是Window是抽象类,唯一实现类为PhoneWindow
// 可直接查看PhoneWindow.superDispatchTouchEvent(ev)方法
// 顺藤摸瓜下去就可以看到实际上调用的是ViewGroup.dispatchTouchEvent(ev)方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
/**
* 用于和用户交互
* 此方法为空方法,可override
*/
public void onUserInteraction() {
}
/**
* 当前Activity下的任何一个View都没有处理点击事件,就会执行此方法
* 此方法用于处理Window边界外的点击事件
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
Activity
的事件分发代码还是比较简洁的,我们大致理一下流程:
- 接收到
down
事件,执行onUserInteraction()
; - 如果
ViewGroup.dispatchTouchEvent(ev)
返回为true
,那么就直接返回true
,结束; - 如果
ViewGroup.dispatchTouchEvent(ev)
返回为false
,调用自身的onTouchEvent(ev)
方法,结束。
结合下方的流程图更容易理解:
activity事件分发流程
中间人ViewGroup
理解了 Activity
对事件的处理之后,我们趁热打铁来分析一下 ViewGroup
对事件的分发、拦截和消费,因为它涉及到了三个方法,可想而知难度会提升一点。
来看看 ViewGroup
是如何分发、拦截和消费事件的:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 标志是否分发了此点击事件
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// 判断是否拦截此点击事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 可以拦截
if (!disallowIntercept) {
// 调用拦截事件方法,默认返回false
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// 如果没有被取消而且没有被自身拦截,那就向下(子View)分发
if (!cancel && !intercepted) {
// 循环每个子View,调用子View的dispatchTouchEvent
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 实际就是调用子View的dispatchTouchEvent(ev)
if(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)){
// 如果子View分发成功,那么将child赋值到mFirstTouchTarget对象的next指针中
// mFirstTouchTarget是TouchTarget对象的引用,TouchTarget类似链表结构
addTouchTarget(child,idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
}
}
}
// 如果mFirstTouchTarget为空,说明没有执行上面的addTouchTarget(child,idBitsToAssign)方法
// 那就代表事件要么被取消了,要么被拦截了,这时候dispatchTransformedTouchEvent()第三个参数传的是null,会在下面介绍
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}else{
// alreadyDispatchedToNewTouchTarget在上面循环的时候,如果分发成功,它就为true,handled就为true
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
}
}
return handled;
}
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
// 如果子View为空,那么就调用super.dispatchTouchEvent(ev) == View.dispatchTouchEvent(ev)
// ViewGroup是View的子类,所以在super中的事件将被ViewGroup代替
handled = super.dispatchTouchEvent(event);
} else {
// 子View不为空,调用View.dispatchTouchEvent(ev)
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
}
/**
* 可覆写,返回true表示拦截,false表示不拦截
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
上面的注释一定要仔细看,看完如果还有点不理解,没关系,我们接着理一下 ViewGroup
的整体思路:
- 在
dispatchTouchEvent()
方法中,首先执行onInterceptTouchEvent(ev)
方法,看是否被拦截; - 如果被拦截,那么就不分发事件到子
View
的dispatchTouchEvent(ev)
,而是分发到super.dispatchTouchEvent()
,由于ViewGroup
的父类就是View
,所以还是会执行View.dispatchTouchEvent(ev)
方法,这里先不急着弄懂这步,只要知道流程就行,将会在下节介绍; - 如果没有被拦截,直接循环子
View
,调用子View
的dispathcTouchEvent(ev)
方法。
到这里大致的流程就清晰了,再结合下方的流程图加深下理解:
ViewGroup事件分发流程
最后接收人View
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
ListenerInfo li = mListenerInfo;
// 监听信息不为空
// OnTouchListener不为空,也就是setOnTouchListener()
// 当前view的enable为true
// OnTouchListener接口中的boolean onTouch(View v, MotionEvent event)方法需要覆写
// 缺一不可
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 如果上面不满足,那么看onTouchEvent方法是否返回true,如果返回true,那么result也为true,否则为false
// 其实这个if()也就等于:return onTouchEvent(event),仔细体会下
if (!result && onTouchEvent(event)) {
result = true;
}
return result;
}
public boolean onTouchEvent(MotionEvent event) {
// 只要设置了单击和长按事件,clickable就为true
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
// 调用点击事件
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
return true;
}
return false;
}
View
的事件分发相当于 ViewGroup
就简单多了,流程在源码中也体现的很清楚,我们来理一下整个流程:
- 执行到
dispatchTouchEvent(ev)
的时候,先去调用onTouch(view,ev)
方法; - 如果
onTouch()
返回false
,才去调用下面的onTouchEvent(ev)
方法; - 在
onTouchEvent(ev)
方法内部,可以看见执行了performClickInternal()
方法,这个方法就是我们常见的onClick()
以上三点都是需要满足种种条件才可执行的,条件都在源码注释中详细解释了,大家一定要把这些条件看清楚了。
到这里我们需要解决在 ViewGroup
中留下的一个问题,那么就是当 ViewGroup
没有子 View
的时候,调用了一个 super.dispatchTouchEvent(ev)
方法,其实就是把 ViewGroup
的 onTouch()
、onTouchEvent()
和 onClick()
三个方法按照条件来执行,千万不要以为调用父类的方法还是执行子 View
的事件分发!
View
的事件分发主要流程参考下方的流程图:
事件分发的流程说难吧其实理清了也不难,说不难吧还是挺绕的,最后总结下几点:
-
事件分发的顺序是:
Activity
->ViewGroup
->View
; -
Activity
和View
都没有拦截事件,Activity
如果存在拦截事件,那么整个页面都响应不了点击事件了,View
因为是最后一层,拦不拦截都没必要了; -
ViewGroup
在无子View
接收分发事件或子View
分发事件返回false
的时候,会调用自身的onTouch
、onTouchEvent()
和onClick()
方法。
大家一定要好好体会Android事件分发机制,无论是在日常开发中还是面试中,都是必不可缺的知识点!源码分析的文章还在不断的更新,如果本文章你发现有不正确或者不足之处,欢迎你在下方留言或者扫描下方的二维码留言也可!
网友评论