01为什么会出现 ACTION_CANCEL?
在 Android 的 MotionEvent 家族里,ACTION_DOWN、ACTION_UP、ACTION_MOVE 常常是开发者眼中的“明星”,而低调的 ACTION_CANCEL 却扮演着同样关键的收尾角色。它并不常出现,却能在“指尖离开屏幕”的那一刻,决定滑动事件能否被正常回收,也决定了一个 View 是否还能继续“营业”。
想摸清 ACTION_CANCEL 的来龙去脉,得先回到事件分发的源头——ViewGroup 的 dispatchTouchEvent 方法。事件从父容器一路传递到子 View,当子 View 处理完事件后,父容器会调用这个方法。如果此时子 View 被移除,或即将被移除,系统就会顺手抛出 ACTION_CANCEL,告诉下游:别再把事件往这个“消失”的 View 里塞了。
02三种最常见的 ACTION_CANCEL 触发场景
2.1 ▍ 子 View 被显式移除手指刚按下,3 秒后代码执行 viewGroup.removeAllViews(),View 被瞬间摘除,系统立即在父容器里发出 cancel 信号。测试代码只需两行:
```java
viewGroup.postDelayed(new Runnable() {
public void run() {
viewGroup.removeAllViews();
}
}, 3000);
```
2.2 ▍ Window 层面强制移除当调用 getWindowManager().removeView(getWindow().getDecorView()) 时,系统会走到 dispatchDetachedFromWindow 方法,同样会 清空触摸目标并发送 cancel 事件。测试逻辑类似,只是把“3 秒延迟”换成 Window 层面的移除指令。
场景一:代码主动 remove 子 View
场景二:Window 层面强制摘除 DecorView
场景三:View 被动画移出屏幕且动画结束(mTransitioningViews 为空)
三种情况都会触发 cancelTouchTarget 与 cancelHoverTarget,告诉输入系统:这个 View 不再消费事件,请把后续事件分给别的兄弟。

03手指滑出 View 时,事件到底去哪了?
3.1 ▍ 单指滑动的标准流程DOWN——在 View 上按下
MOVE——手指移动,坐标仍在 View 边界内
UP——手指抬起,事件正常结束

上图演示了典型场景:按下后滑动,坐标冲出 View 边界,却在父容器上继续滑行。此时 MOVE 与 UP 事件依旧会先抵达原 View,因为事件分发机制只看坐标是否落在“目标 View”内。若此时触发长按或点击动作,系统会判断:
若坐标落在原 DOWN 坐标处,则正常响应;
若坐标已漂移到父容器,则逻辑上“理应不响应”。
3.3 ▍ 源码如何判断“漂移”?在 View 的 onTouchEvent 里,系统通过 event.getX()、event.getY() 获取当前坐标,与 DOWN 时坐标对比:
若超出原 DOWN 坐标的一定阈值(可通过 viewConfiguration.getScaledTouchSlop() 获取),则认为“漂移”;
若漂移发生且时间差满足长按阈值,系统便放弃长按或点击识别,实质上就是另一种“隐形”的 ACTION_CANCEL——坐标不再落在原目标上,自然无需再消费事件。
04小结:指尖离开的那一刻,事件被悄悄“拦截”了
代码层面:removeView、removeViewAt、Window.removeView 等操作都会触发 ACTION_CANCEL。
滑动层面:手指滑出原 DOWN 坐标边界后,系统默认“放弃识别”,相当于另一种隐形 cancel;
处理建议:若希望在“离开”瞬间做清理或回退操作,可在 onTouchEvent 的 ACTION_UP 里加判断逻辑;若希望防止“漂移”导致误触,可在 ACTION_DOWN 时记录起始坐标,并在 ACTION_UP 里做坐标差比较。
把 ACTION_CANCEL 想成屏幕上的“收场哨声”——它不一定华丽,却能在关键时刻让事件回归正常轨道。下次调试滑动冲突或动画消失时,不妨回头看看这条被忽略的信号线。
原创文章,作者:孙杰,如若转载,请注明出处:http://m.gaochengzhenxuan.com/keji/14365.html