事件分发初探
- 一、概述
- 1.1 事件分发概述
- 1.2 事件的类型
- 1.3 什么是事件序列?
- 1.4 事件分发过程中共同协作的方法。
- 1.5 事件在哪些对象之间传递?传递的顺序是什么?
- 1.6 事件的传递过程
- 1.由上而下的传递过程
- 2.由下而上的传递过程
- 1.7 ViewGroup怎么通过dispatchTouchEvent()把事件传递给自己的onTouchEvent?
- 1.8 View有自己的dispatchTouchEvent()方法吗?
- 1.9 dispatchTouchEvent()中希望让自己的`onTouchEvent()`处理怎么操作?
- 1.10 dispatchTouchEvent()中希望传给子View怎么操作?
- 1.11 希望不做处理、终止向下传递、开始回溯,怎么操作?
- 1.12 onTouch和onTouchEvent的区别是什么?
- 1.13 事件分发传递规则
- 二、事件分发源码分析
- 2.1 流程1:Activity的事件分发机制
- 2.2 流程2:ViewGroup的事件分发机制
- 2.3 流程3:View的事件分发机制
- 返回值分析:
- 三、事件分发三个方法的流程图
- 3.1 dispatchTouchEvent()流程图
- 3.2 onInterceptTouchEvent()流程图
- 3.3 onTouchEvent()流程图
- 四、事件传递的几种情况
- 4.1 默认情况
- 4.2 处理事件
- 4.3 拦截DOWN事件
- 4.4 拦截事件列中间事件
一、概述
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 (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}*/
/***交给Activity附属的Window分发**
如果getWindow().superDispatchTouchEvent(ev)为true 代表事件循环结束
Activity的dispatchTouchEvent就返回ture
*/
//如果getWindow().superDispatchTouchEvent(ev)为false 意味着没处理,执行onTouchEvent()
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Window
是一个抽象类,其DispatchTouchEvent()
也是抽象方法,该类可以控制顶级View的外观和行为策略。该类唯一实现是PhoneWindow,其中有一个内部类DecorView
是一个ViewGroup
。PhoneWindow
中处理点击事件:
public boolean superDispatchTouchEvent(MotionEvent event) {
// mDecor 是 顶级View(DecorView)的实例对象
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) {
...
/*调用**ViewGroup的onInterceptTouchEvent()**进行判断,
返回true表示ACTION_DOWN被VIEWGROUP拦截,返回false则表示事件继续传递给子View*/
final boolean intercepted;//intercepted变量表示**是否**拦截该次事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
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;
}
if (!canceled && !intercepted) { // 没取消也不拦截,即是个有效的touch事件
if (actionMasked == MotionEvent.ACTION_DOWN // 第一个手指down
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) // 接下来的手指down
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) { // 基本都成立
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
// 从最后一个向第一个遍历ViewGroup下所有子View
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; // 不满足这2个条件直接跳过,看下一个child
}
// child view能receive touch事件而且touch坐标也在view边界内
newTouchTarget = getTouchTarget(child);// 查找child对应的TouchTarget
if (newTouchTarget != null) { // 比如在同一个child上按下了多跟手指
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
//子View已经在自己的范围内得到了触摸。
//除了它正在处理的那个,给它一个新的指针。
newTouchTarget.pointerIdBits |= idBitsToAssign;
break; // newTouchTarget已经有了,跳出for循环
}
resetCancelNextUpFlag(child);
// 将此事件交给child处理
// 有这种情况,一个手指按在了child1上,另一个手指按在了child2上,以此类推
// 这样TouchTarget的链就形成了
// 进行子View的分发
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 如果处理掉了的话,将此child添加到touch链的头部
// 注意这个方法内部会更新 mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true; // down或pointer_down事件已经被处理了
break; // 可以退出for循环了。
}
}
}
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;
//仅分析核心代码
// Perform any necessary transformations and dispatch.
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());
}
//分发给子View的dispatchTouchEvent
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
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;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//先判断是否设置OnTouchListener,
//如果OnTouchListener中onTouch()返回true,onTouchEvent()就不会被调用,
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//从这里看出,如果既设置了onTouchListener又设置了onClickListener,
//如果OnTouchListener中onTouch()返回true,onTouchEvent()不会被调用,onClickListener也不会被触发
//onTouch中每次收到Touch事件,但onClickListener只在ACTION_UP来时触发
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
View的onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {// View对touch事件的默认处理逻辑
...
if ((viewFlags & ENABLED_MASK) == DISABLED) {// DISABLED的状态下
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);//如果之前是Pressed状态则复原
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
//如果有mTouchDelegate,优先交给它处理
if (mTouchDelegate.onTouchEvent(event)) {
return true;// 处理了返回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;
}
// 如果外围有可以滚动的parent的话,当按下时会设置这个标志位
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
// 按下了或者预按下了
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
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)) {
// If the post failed, unpress right now
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;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {// 如果是在可以滚动的container里面的话
mPrivateFlags |= PFLAG_PREPRESSED;// 设置PREPRESSED标志位
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);// 否则直接显示pressed feedback
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)) {// 如果移动到view的边界之外,
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* mAmbiguousGestureMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= mAmbiguousGestureMultiplier;
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {// 如果移动到view的边界之外,
// Outside button
// Remove any future long press/tap checks
removeTapCallback();// 则取消Tap callback,这样当你松手的时候onClick不会被触发
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {// 当已经是按下状态的话
setPressed(false);// 恢复按下状态
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;// 最后返回true,表示对touch事件被处理,被消费
}
return false;// 既不能单击也不能长按的View,返回false,表示不处理touch事件
}
返回值分析:
- 如果
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()
处理。