一、概述
1.1 事件分发概述
**答:**当用户触摸屏幕(View 或 ViewGroup 派生的控件),将产生点击事件(Touch 事件)。Touch 事件的相关细节(发生触摸的位置 、时间 等)被封装成MotionEvent 对象,系统需把这个事件传递给一个具体的 View 去处理(消费)以及处理(消费)的整个过程。**即事件传递的过程及处理的整个过程,**其可能经过的对象有最上层Activity,中间层ViewGroup,最下层View。
1.2 事件的类型
事件类型 | 具体动作 |
---|
MotionEvent.ACTION_DOWN | 按下View(所有事件的开始) | MotionEvent.ACTION_UP | 抬起View(与DOWN对应) | MotionEvent.ACTION_MOVE | 滑动View | MotionEvent.ACTION_CANCEL | 结束事件(非人为原因) |
1.3 什么是事件序列?
从手指接触屏幕 至 手指离开屏幕,这个过程产生的一系列事件是同一个事件序列。 一般情况下,事件序列以DOWN事件开始、UP事件结束,中间有无数的MOVE事件.
1.4 事件分发过程中共同协作的方法。
答:dispatchTouchEvent() 、onInterceptTouchEvent() 和onTouchEvent() 。
dispatch :派遣、发出、传递。 intercept :拦截
方法 | 作用 | 调用时刻 |
---|
dispatchTouchEvent() | 分发(传递)点击事件 | 点击事件(Touch)传递给当前View时,dispatchTouchEvent()就会被调用。 | onTouchEvent() | 处理点击事件 | 在dispatchTouchEvent()内部调用 | onInterceptTouchEvent() | 判断是否拦截了某个事件(只存在于ViewGroup之中) | 在ViewGroup的dispatchTouchEvent()内部调用 |
1.5 事件在哪些对象之间传递?传递的顺序是什么?
答:当产生点击事件后会先由Activity 来处理,在Activity , ViewGroup , View 之间传递。
1.6 事件的传递过程
1.由上而下的传递过程
当一个View 或ViewGroup 的onInterceptTouchEvent() 返回true ,表示它要拦截这个MotionEvent ,会调用它的dispatchTouchEvent() ,返回false则将MotionEvent 传递给子元素的dispatchTouchEvent 。最后传给最底层View ,无法再向下传递,就由底层View 的onTouchEvent() 处理。
2.由下而上的传递过程
如果onTouchEvent() 返回true 表示该View处理了,处理后逐层向dispatchTouchEvent() 返回直至结束,返回false 则代表没处理,继续向上层View 的onTouchEvent() 传递,最后由Activity的onTouchEvent() 处理,不论处理结果是什么都结束事件分发。
1.7 ViewGroup怎么通过dispatchTouchEvent()把事件传递给自己的onTouchEvent?
答:dispatchTouchEvent() return true或false都不行,只能在其内部通过Interceptor() 将事件拦截并调用自己的onTouchEvent() ,因此ViewGroup 的dispatchTouchEvent() super默认实现就是去调用此ViewGroup 的onInterceptTouchEvent() 进行拦截。
1.8 View有自己的dispatchTouchEvent()方法吗?
答:有,但View没有onInterceptTouchEvent() 来判断是将事件传给子View 还是拦截,因此View 调用super.dispatchTouchEvent() 时默认把事件传给自己的onTouchEvent() 处理(拦截)。dispatchTouchEvent() 中返回false就回溯父元素的onTouchEvent() 由父元素处理。
1.9 dispatchTouchEvent()中希望让自己的onTouchEvent() 处理怎么操作?
答:默认处理super.dispatchTouchEvent() 会调用自己的onInterceptTouchEvent() ,在其中return true 表示拦截就会把MotionEvent 分发给自己的onTouchEvent() 处理。
1.10 dispatchTouchEvent()中希望传给子View怎么操作?
答:默认处理super.dispatchTouchEvent() 会调用自己的onInterceptTouchEvent() ,在其中return false (不拦截且不消费),就会将MotionEvent 交给子类。
1.11 希望不做处理、终止向下传递、开始回溯,怎么操作?
答:在每一层View的onTouchEvent() 中return false;
1.12 onTouch和onTouchEvent的区别是什么?
答:两个方法都是在View.dispatchTouchEvent() 中调用,源码如下:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
可以看到onTouch() 优先于onTouchEvent() 执行,如果onTouch方法中返回true将事件消费,onTouchEvent就不会执行。
onTouch() 的执行需要mOnTouchListener 不为空且当前点击的控件为ENABLED ,对于非ENABLED控件(不可点击控件) ,我们如果想要监听其touch事件应该重写onTouchEvent() ,因为它的onTouch() 永远不会得到执行!
1.13 事件分发传递规则
- 一般情况下,一个事件序列只能被一个View拦截消耗,一旦拦截事件序列中所有事件会交给此View处理,不能由两个View同时处理,除非在
onTouchEvent() 将MotionEvent传递给其他View。 - 一个
View 的onTouchEvent() 返回了false,那么同一事件序列都不会再交给此View处理,而是重新交给父元素的onTouchEvent() 。 - 一旦某个View决定拦截,此View的
onInterceptTouchEvent() 不会再被调用,不会再次询问去询问它是否拦截。 - 当
dispatchTouchEvent() 和 onTouchEvent() 都return true,事件传递就到达了终点(被消费),不会再继续传递。 - 当
dispatchTouchEvent() 事件分发时,只有前一个事件(如ACTION_DOWN)返回true,才会收到后一个事件(ACTION_MOVE和ACTION_UP) - 若对象
(Activity、ViewGroup、View) 的dispatchTouchEvent() 分发事件后消费了事件(返回true),那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP! - 若对象
(Activity、ViewGroup、View) 的onTouchEvent() 处理了事件(返回true ),那么ACTION_MOVE、ACTION_UP的事件从上往下传到该View 后就不再往下传递,而是直接传给自己的onTouchEvent() ,结束本次事件传递过程!
二、事件分发源码分析
2.1 流程1:Activity的事件分发机制
Android的事件分发机制首先将MotionEvent 传递给Activity的dispatchTouchEvent() 进行事件分发。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Window 是一个抽象类,其DispatchTouchEvent() 也是抽象方法,该类可以控制顶级View的外观和行为策略。该类唯一实现是PhoneWindow,其中有一个内部类DecorView 是一个ViewGroup 。PhoneWindow 中处理点击事件:
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
顶级View ,即在Activity 中通过setContentView() 设置的View ,也叫根View ,此处调用的是ViewGroup的dispatchTouchEvent() 方法,从而实现了事件由Activity→ViewGroup的dispatchTouchEvent() 的过程。
流程图和方法图非常详尽:
ViewGroup 的dispatchTouchEvent() 什么时候返回true / false ?接下来ViewGroup 的事件分发流程会进行说明。
2.2 流程2:ViewGroup的事件分发机制
由Activity的事件分发机制可知,Activity.dispatchTouchEvent() 中getWindow().superDispatchTouchEvent(ev) 中super.dispatchTouchEvent(MotionEvent event) 实现了Activity->ViewGroup 的传递,ViewGroup 的事件分发机制也由dispatchTouchEvent() 进行。
public boolean dispatchTouchEvent(MotionEvent ev) {
...
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = **onInterceptTouchEvent(ev)**;
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
ViewGroup会用**onInterceptTouchEvent()**进行判断是否对该事件进行拦截,默认不拦截,如果拦截就用的onInterceptTouchEvent() 会向View 的dispatchTouchEvent() 分发。
ViewGroup的onInterceptTouchEvent()源码
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的dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)源码
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
final boolean handled;
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
遍历ViewGroup 的子View 或子ViewGroup ,判断此次点击事件触摸区域是否属于它的子View 或子ViewGroup 的区域,如果属于子View区域就调用子View的dispatchTouchEvent() ,即传递给子View处理,如果属于子ViewGroup,继续遍历子ViewGroup子视图,直到找到处理事件的View。如果遍历到最后还找不到就回调上一层的onTouchEvent() 直到Activity。
流程图:
2.3 流程3:View的事件分发机制
View的事件分发同样由dispatchTouchEvent() 开始
public boolean dispatchTouchEvent(MotionEvent event) {
...
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
View的onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {
...
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL://针对CANCEL事件,恢复各种状态,移除各种callback
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
if (!pointInView(x, y, touchSlop)) {
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* mAmbiguousGestureMultiplier);
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= mAmbiguousGestureMultiplier;
}
if (!pointInView(x, y, touchSlop)) {
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
removeLongPressCallback();
checkForLongClick(
0 ,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
返回值分析:
- 如果
getWindow().superDispatchTouchEvent() 返回true,也就是ViewGroup.dispatchTouchEvent() 返回true,则方法结束,Activity的dispatchTouchEvent() 也返回true,代表点击事件顺利从Activity传递到ViewGroup,ViewGroup分发后事件在其中被消费,Activity的分发任务结束,如果Activity的onTouchEvent() 返回false,则Activity的dispatchTouchEvent() 返回false,则代表事件没有被消费。 - 如果getWindow().superDispatchTouchEvent()方法返回false,则执行
Activity 的onTouchEvent() 方法,无论该方法返回true 还是false ,都表示此次事件分发都结束,ture表示点击事件在Window 边界外(事件被Activity 消费),false 在Window 边界内(事件不被处理,不算Activity 消费)。
三、事件分发三个方法的流程图
3.1 dispatchTouchEvent()流程图
3.2 onInterceptTouchEvent()流程图
需要注意:只有ViewGroup 有这个方法,Activity 和View 都没有
3.3 onTouchEvent()流程图
四、事件传递的几种情况
4.1 默认情况
不对控件里的方法(dispatchTouchEvent() 、onTouchEvent() 、onInterceptTouchEvent() )进行重写。
需要注意的是:onTouchEvent() 如果对DOWN 事件返回了false,那么将不会再接收处理事件列的其他事件。但ViewGroup 的onInterceptTouchEvent() 对DOWN 事件返回了false 后后续事件仍会传递到它的onInterceptTouchEvent()
4.2 处理事件
如果View V 希望处理点击事件,那么设置View V 的(Clickable) 或重写onTouchEvent() 返回true
事件传递情况
- DOWN事件被传递给
V 的onTouchEvent() 并返回true,表示V要处理该事件 - V正在处理该事件,所以不会将
DOWN 传递给ViewGroup 和Activity 的onTouchEvent() - 事件列中其他事件也会传递给
V 的onTouchEvent() - 如下图,也就是逐层向
dispatchTouchEvent() 返回直至结束。
4.3 拦截DOWN事件
如果希望ViewGroup vg处理该点击事件,重写其onInterceptTouchEvent()返回true,onTouchEvent()返回true。
onInterceptTouchEvent() 返回true表示事件不再向下传递- 自身
onTouchEvent() 处理事件,不会传递给Activity的onTouchEvent() - 事件列内其他事件都传递给vg的
onTouchEvnet() ,且不会再传递给vg 的onInterceptTouchEvent() ,因为一旦onInterceptTouchEvent() 返回true 就再也不会被调用。 - 逐层向
dispatchTouchEvent() 返回,结束事件分发。
4.4 拦截事件列中间事件
如果ViewGroup vg 拦截了一个MOVE ,那么该事件会变为CANCEL 并传递给在处理该事件的子 View,且不传递给vg 的onTouchEvent() ,vg 的onTouchEvent() 会接收新到来的MOVE 事件。
例如vg的子View v处理DOWN事件(v的onTouchEvent() 返回true ),但vg 拦截了MOVE事件(vg 的onInterceptTouchEvent() 返回true拦截了MOVE),这个MOVE会变成CANCEL传递给v的onTouchEvent() ,后续新来的MOVE会传递给vg的onTouchEvent() ,而不是vg的onInterceptTouchEvent() 。也就是后续事件直接由vg的onTouchEvent() 处理。
|