前言
首先,此文章主要讲解View的Touch事件分发机制,旨在弄清楚事件分发的主要原理,因此,观其大略而不拘泥技术细节。
总览
Touch事件的分发,围绕着dispatchTouchEvent() 、onInterceptTouchEvent()、onTouchEvent()做文章,弄清楚三者的触发时机,也就弄清楚了机制。
dispatchTouchEvent();
onInterceptTouchEvent();
onTouchEvent();
入口
首先,一个UI的开始,是一个ViewGroup,因此,可以得知Touch事件先由ViewGroup接手,下面是ViewGroup的dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
...... // 省略代码
// 是否处理(消费)了此次事件
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
.......//省略代码
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 是否拦截此次Touch事件,默认为false
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
// 走到这里表示没有可以被Touch的目标
// 因此当作拦截
intercepted = true;
}
......// 省略代码
// 没被取消且没被拦截
if (!canceled && !intercepted) {
......// 省略代码
//所有子View
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
......// 省略代码
// 通过dispatchTransformedTouchEvent调用View.disapatchEvent()
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
......// 省略代码
}
}
return handled
}
源码的代码还是很长的,已经做了很多的省略截取了关键部分。在dispatchTouchEvent()里,用handled记录Touch事件是否被消费。首先,先通过onInterceptTouchEvent()判断此ViewGroup是否拦截事件,如果拦截,最终会返回true,如果ViewGroup上没有可以Touch的目标,就默认为拦截,这个应该比较好理解。如果不拦截并且事件没有被取消,会通过dispatchTransformedTouchEvent()将事件分发给子View。
事件拦截
onInterceptTouchEvent()负责被表明是否拦截Touch事件,下面是源码
/**
* Implement this method to intercept all touch screen motion events. This
* allows you to watch events as they are dispatched to your children, and
* take ownership of the current gesture at any point.
*/
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;
}
此函数的作用,注释说的很明白: 实现此方法中断Touch事件(默认为不中断,返回false). 不赘述
分发事件到子View
如果事件没有被ViewGroup消费,那么ViewGroup会通过dispatchTransformedTouchEvent()将Touch事件分发到子View里
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
......// 省略
return handled;
}
dispatchTransformedTouchEvent()会根据child情况的不同分别调用super.dispatchTouchEvent(event)和child.dispatchTouchEvent(event),而ViewGroup继承子View,因此,自然会调用到View的dispatchTouchEvent()
View的dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent event) {
// 检查当前View是否可以获得焦点,由此可见,能获得焦点是能被Touch的前提
if (event.isTargetAccessibilityFocus()) {
// 无法获得焦点,无法消费此次事件
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
...... // 省略
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
// ListenerInfo存有各种Listener信息,如OnClickListener,OnTouchListener,OnFocusChangeListener等等
ListenerInfo li = mListenerInfo;
// 调用 OnTouchListener.onTouch()
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
// 设置了OnTouchListener会优先调用,优先级比onTouchEvent高
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
...... // 省略
return result;
}
当调用到dispatchTouchEvent()时,会先去看看当前View是不是可以获得焦点,在可以的情况下再进行下一步。如果当前View设置了OnTouchListener,会优先调用OnTouchListener.onTouch();没有设置OnTouchListener.onTouch或者OnTouchListener.onTouch.onTouch()返回false,则再去调用onTouchEvent()
消费事件onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {
...... // 省略
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...... // 省略
// 执行点击事件
performClick();
...... // 省略
}
return true;
}
return false;
}
onTouchEvent()默认返回false,代表Touch事件没有被消费,可以根据需要重写onTouchEvent()。当然,就算再onTouchEvent()做了相应的处理,也是可以继续将事件传递下去,只要不要返回true就可以。 在onTouchEvent()里,还会根据手势的不同,执行不同的动作。
回顾
Touch事件从ViewGroup开始,通过dispatchTouchEvent()来分发事件。首先,会通过onInterceptTouchEvent()来判断是否要拦截此次事件,如果拦截,就代表ViewGroup会处理此次事件;反之,会逐一访问子View,子View再根据自己的onTouchEvent()来判断是否处理(消费)此次事件,如果子View设置了OnTouchListener并且OnTouchListener.onTouch()返回true,则不会调用到onTouchEvent(),认为消费了此次事件。一旦有一个子View消费了事件,Touch事件便不会再传递。流程如下图
而这样的传递机制,是一种责任链。 什么事责任链?简单来说,就说接到一个任务,先看看自己做不做,做了就算完成(被消费),否则交给下级去做,只要有其中一个下级做了,这件任务就算完成了。 重复这个过程,直到任务被完成(被消费)。或者遍历了完所有,并没有谁来完成这件任务。如图
当收到一个Touch事件时,如果自身不消费,会向下传递,当都没有被消费,逐层返回false,最后认为事件没有被消费;反之,如在图中节点4事件被消费,逐层返回true,代表事件被消费
总结
1、Touch事件的第一个接受者为ViewGroup
2、ViewGroup 可以对Touch事件进行拦截,如果不拦截则向子View分发
3、如果View设置了OnTouchListener,当接受到Touch事件时会调用OnTouchListener,且消费了此次事件,不会调用到onTouchEvent()
4、View通过onTouchEvent()返回了true,则认为消费了事件