Android事件分发:ACTION_CANCEL的非正常事件序列终结符

593 阅读3分钟

一句话总结

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 显示异常。

  • 状态重置ViewACTION_DOWN 时通常会改变其状态,如改变背景颜色、高亮显示等。ACTION_CANCEL 的主要作用就是让 View 知道,当前的“点击”操作已经作废,需要将所有临时状态恢复到初始状态。
  • 防止逻辑错误:如果没有 ACTION_CANCEL,当事件被中断时,View 可能永远无法收到 ACTION_UP 事件,导致其状态(如高亮)一直持续,造成 UI 错误。

三、代码处理示例与最佳实践

在自定义 ViewViewGrouponTouchEvent()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. 如何避免滑动冲突?

  • 核心思路:在父 ViewGrouponInterceptTouchEvent() 中,根据滑动方向和业务逻辑,动态地返回 true(拦截)或 false(不拦截)。
  • 当父 ViewGroup 拦截事件时,子 View 会收到 ACTION_CANCEL,从而取消其点击状态,将事件交由父 ViewGrouponTouchEvent() 处理。

2. ACTION_UPACTION_CANCEL 的区别?

  • ACTION_UP:标志着触摸事件序列的正常结束。通常在抬起手指且没有被拦截的情况下触发。
  • ACTION_CANCEL:标志着触摸事件序列的非正常结束。通常由父 ViewGroup 拦截或系统强制打断时触发。

3. ACTION_DOWNACTION_MOVEonInterceptTouchEvent 中返回 true 有什么区别?

  • 如果 onInterceptTouchEventACTION_DOWN 时返回 true,那么后续的所有事件(MOVEUP)都将直接由 ViewGroup 处理。
  • 如果 onInterceptTouchEventACTION_MOVE 时返回 true,那么 ACTION_DOWN 事件仍然会传递给子 View,但 MOVEUP 事件将被 ViewGroup 拦截。此时,系统会向子 View 发送一个 ACTION_CANCEL 事件。