应用必须处理取消事件,否则会遗留未完成的按压态
别名: 按压态残留 · 卡住高亮 · 未处理 cancel
概念解释
控件若只在结束事件里清除按压态(pressed state),遇到取消就会把高亮、缩放、拖影或“正在发送”留在屏幕上。用户已经不再触摸,界面却仍显示按着。这不是视觉皮肤问题,而是状态机少了一条终止边:开始把控件送进候选,却没有对称的出口把候选拆掉。
机制
按下通常会改三处状态:绘制(背景加深)、内部标志(isPressed)、以及可能已经启动的定时器或事务(长按计时、滚动捕获、网络请求预备)。结束事件会把这三处一起收回。取消到达时,如果处理函数空着或直接 fall-through 到“忽略未知类型”,这三处都不会动。下次真正的开始到来时,旧标志仍为真,于是出现双高亮、点了空白却触发旧按钮、或拖动累计了一次幽灵位移。更隐蔽的是捕获了触摸的滚动容器:它以为自己还在跟踪手指,会把后续无关触点吞掉。
怎么研究
做一对对照:同一按钮分别实现“只处理 end”和“end 与 cancel 走同一清理函数”。在按住后插入系统打断,拍摄按压高亮是否残留,再立刻点屏幕其他位置,看旧动作是否仍触发。因变量用残留时长、误触发次数、以及滚动容器在打断后是否还能把后续触点交给别的控件。自变量包括控件类型(瞬时按钮、可切换按钮、滑块)和打断发生在长按阈值前还是后。不要只用视觉检查,因为内部标志残留在下一帧可能暂时看不出来。
边界
有些声明式框架会在窗口失活时强制重绘,看起来“没处理 cancel 也恢复了”,那是框架替你拆了按压态,不能推广到自定义 Canvas 控件或游戏 HUD。相反,处理了 cancel 也不等于可以提交:取消路径必须禁止副作用。若应用在开始时就已经发出不可撤回的请求,再完美的清理也救不了;那是把提交点放错了,不是事件处理遗漏。无障碍模拟点击通常不走触摸取消,所以只测屏幕手指会漏掉开关和键盘路径上的类似卡住。
怎么落地
- 把清除按压绘制、内部标志和进行中定时器抽成一个函数,让 end 与 cancel 都调用它;cancel 分支禁止提交。
- 在 QA 脚本里加一步:按住支付或发送按钮后拉下通知栏,检查高亮、文案和网络请求都回到按下前。
- 对自绘控件和 Web 的
touchcancel/pointercancel写单测,断言 pressed 标志为假且没有 click 回调。