一句话总结
ACTION_CANCEL 就像快递被截胡了,原本要送到你手里的包裹(触摸事件),中途被系统或父控件拦截了,必须通知你“别等了,包裹不送了!”
一、ACTION_CANCEL的触发场景
ACTION_CANCEL 是一个特殊的 MotionEvent,它标志着当前的触摸事件序列因某些外部因素而非正常结束。它总是作为事件序列中的最后一个事件被发送。
1. 父控件中途拦截
- 这是
ACTION_CANCEL最常见的触发场景。 - 流程:当一个子
View接收了ACTION_DOWN事件并处理时,如果其父ViewGroup在随后的ACTION_MOVE事件中,其onInterceptTouchEvent()方法返回true,那么父ViewGroup会拦截事件。 - 结果:此时,系统会向子
View发送一个ACTION_CANCEL事件,通知它:事件序列被父容器接管了,不要再处理后续事件了。
2. 系统强制打断
-
当触摸事件序列正在进行时,如果发生一些系统级事件,系统会向当前拥有焦点的
View发送ACTION_CANCEL。 -
典型场景:
- 来电或系统弹窗弹出。
- 应用进入后台。
二、ACTION_CANCEL的核心作用:状态清理
ACTION_CANCEL 的存在,是为了确保 View 在事件序列被中断时,能够正确地清理其内部状态,从而防止 UI 显示异常。
- 状态重置:
View在ACTION_DOWN时通常会改变其状态,如改变背景颜色、高亮显示等。ACTION_CANCEL的主要作用就是让View知道,当前的“点击”操作已经作废,需要将所有临时状态恢复到初始状态。 - 防止逻辑错误:如果没有
ACTION_CANCEL,当事件被中断时,View可能永远无法收到ACTION_UP事件,导致其状态(如高亮)一直持续,造成 UI 错误。
三、代码处理示例与最佳实践
在自定义 View 或 ViewGroup 的 onTouchEvent() 或 onInterceptTouchEvent() 中,正确处理 ACTION_CANCEL 是至关重要的。
// 在 View 的 onTouchEvent 中处理 ACTION_CANCEL
button.setOnTouchListener { view, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 按下时改变背景,记录状态
view.background = pressedBackground
true
}
MotionEvent.ACTION_UP -> {
// 抬起时恢复背景,触发点击
view.background = normalBackground
view.performClick()
true
}
MotionEvent.ACTION_CANCEL -> {
// 事件被中断时,恢复初始状态
view.background = normalBackground
true
}
else -> false
}
}
四、常见问题与解答
1. 如何避免滑动冲突?
- 核心思路:在父
ViewGroup的onInterceptTouchEvent()中,根据滑动方向和业务逻辑,动态地返回true(拦截)或false(不拦截)。 - 当父
ViewGroup拦截事件时,子View会收到ACTION_CANCEL,从而取消其点击状态,将事件交由父ViewGroup的onTouchEvent()处理。
2. ACTION_UP 和 ACTION_CANCEL 的区别?
ACTION_UP:标志着触摸事件序列的正常结束。通常在抬起手指且没有被拦截的情况下触发。ACTION_CANCEL:标志着触摸事件序列的非正常结束。通常由父ViewGroup拦截或系统强制打断时触发。
3. ACTION_DOWN 和 ACTION_MOVE 在 onInterceptTouchEvent 中返回 true 有什么区别?
- 如果
onInterceptTouchEvent在ACTION_DOWN时返回true,那么后续的所有事件(MOVE、UP)都将直接由ViewGroup处理。 - 如果
onInterceptTouchEvent在ACTION_MOVE时返回true,那么ACTION_DOWN事件仍然会传递给子View,但MOVE和UP事件将被ViewGroup拦截。此时,系统会向子View发送一个ACTION_CANCEL事件。