Seesaw扫雷克隆:如何实现按钮UI状态的批量同步更新?
我正在用Seesaw写一个扫雷克隆程序,雷区用按钮组成的grid-panel实现。目前已经完成了点击按钮后用泛洪填充揭开周围空白格子的功能,揭开方式是禁用对应按钮。
但现在遇到了一个视觉问题:虽然没有高开销操作,但按钮状态是逐个更新的,界面会随机零散地显示已揭开的格子,体验很差。我宁愿接受短暂的延迟,也不想看到这种逐步更新的效果。
我的核心点击处理代码如下:
(doseq [b buttons] ; Buttons are the grid cells (let [[tx ty] (sc/user-data b) ; The cell position is stored in the user data tile (b/get-tile-at board tx ty)] ; O(1) lookup (when (:uncovered? tile) (sc/invoke-later ; Because this chunk is actually being executed in a pool (sc/config! b :enabled? false)))))
看起来每个config!调用都会单独触发按钮外观更新,请问有没有办法让所有按钮的状态更新一次性完成,哪怕会有轻微的感知延迟?
问题根源
你当前代码的问题在于每个按钮的更新都被包装在了单独的invoke-later调用中。Swing的事件调度线程(EDT)会逐个处理这些独立的UI任务,每处理一个就会触发一次局部重绘,这就导致了界面零散刷新的现象。
解决方案:把所有更新打包到一个EDT任务中
只需要把整个doseq的更新逻辑放到一个invoke-later(或者invoke-and-wait,如果当前线程不是EDT的话)里,这样所有按钮的状态修改都会在同一个EDT任务中完成,Swing会在所有修改完成后一次性重绘界面,就能避免零散的视觉效果了。
修改后的代码如下:
; 把整个更新逻辑放到一个invoke-later里 (sc/invoke-later (doseq [b buttons] (let [[tx ty] (sc/user-data b) tile (b/get-tile-at board tx ty)] (when (:uncovered? tile) (sc/config! b :enabled? false)))))
额外优化(可选)
如果后续你的更新逻辑变得更复杂(比如需要修改按钮的文本、颜色等多个属性),还可以使用Seesaw的with-buffer宏来进一步优化重绘性能,它会暂时禁用组件的自动重绘,直到所有修改完成后再一次性刷新:
(sc/invoke-later (sc/with-buffer (sc/parent buttons) ; 传入网格面板作为缓冲的根组件 (doseq [b buttons] (let [[tx ty] (sc/user-data b) tile (b/get-tile-at board tx ty)] (when (:uncovered? tile) (sc/config! b :enabled? false))))))
这样做的原理是暂时关闭父组件的重绘开关,完成所有子按钮的状态修改后再打开,确保只触发一次完整的重绘,视觉上就是所有格子同时被揭开。
内容的提问来源于stack exchange,提问作者Carcigenicate

