沿路径移动物体:如何让Swing Timer的actionPerformed完成后再触发?
解决Swing Timer顺序执行actionPerformed的问题(路径移动场景)
嘿,我完全懂你的困扰——Swing Timer默认是按设定间隔无脑触发,根本不管上一次的actionPerformed有没有执行完,这对路径移动这种需要按顺序、一步步完成的场景来说简直是灾难。刚好我之前做类似的格子移动动画时踩过同样的坑,给你几个适配你Square路径移动场景的方案:
方案一:单Timer控制路径分步执行(最适合纯UI动画)
这个方案核心是用一个索引跟踪当前走到路径的哪一步,每次Timer触发只处理一个路径点的移动,完成后再步进索引,直到走完整个路径。如果需要平滑移动(不是瞬间跳转到下一个Square),还可以把单个Square的移动拆成多小步来做。
代码示例(瞬间跳转版)
首先定义几个成员变量存状态:
private Timer moveTimer; private Square[] path; // A*生成的路径数组 private int currentStep = 0; // 当前路径索引
初始化Timer并启动:
// 假设path已经由A*算法生成完毕 moveTimer = new Timer(500, e -> { // 先判断是否已经走完路径 if (currentStep >= path.length) { moveTimer.stop(); return; } // 获取当前要移动到的目标Square Square target = path[currentStep]; // 执行移动逻辑:替换成你实际的代码,比如更新Square位置、重绘界面 moveSquareToTarget(target); // 移动完成后,步进索引 currentStep++; }); // 启动移动 moveTimer.start();
代码示例(平滑移动版)
如果需要从当前Square平滑过渡到下一个,把单个Square的移动拆成多小步处理:
private Timer moveTimer; private Square[] path; private int currentPathIndex = 0; private int moveProgress = 0; // 当前Square内的移动进度 private static final int STEPS_PER_SQUARE = 15; // 每个Square拆成15步移动 private Square currentPos; // 当前位置的Square private Square targetSquare; // 当前要移动到的目标Square // 初始化移动逻辑 private void initPathMovement() { currentPos = getInitialSquare(); // 获取初始位置 path = generatePathByAStar(); // 你的A*路径生成方法 if (path.length > 0) { targetSquare = path[currentPathIndex]; } // 用短间隔Timer实现平滑动画 moveTimer = new Timer(30, e -> { if (currentPathIndex >= path.length) { moveTimer.stop(); return; } if (moveProgress < STEPS_PER_SQUARE) { // 计算每一小步的位移 int deltaX = (targetSquare.getX() - currentPos.getX()) / STEPS_PER_SQUARE; int deltaY = (targetSquare.getY() - currentPos.getY()) / STEPS_PER_SQUARE; // 更新当前位置 currentPos.setX(currentPos.getX() + deltaX); currentPos.setY(currentPos.getY() + deltaY); // 重绘界面刷新显示 repaint(); moveProgress++; } else { // 到达当前路径点,切换到下一个目标 currentPos = targetSquare; currentPathIndex++; moveProgress = 0; if (currentPathIndex < path.length) { targetSquare = path[currentPathIndex]; } } }); } // 启动时调用 initPathMovement(); moveTimer.start();
方案二:处理耗时任务的顺序执行(适合带后台计算的场景)
如果你的moveSquareToTarget里有耗时操作(比如复杂计算、文件IO),绝对不能直接在EDT线程执行(会导致界面假死),这时候可以用SwingWorker把耗时任务放到后台,任务完成后再触发下一次移动:
private Timer moveTimer; private Square[] path; private int currentStep = 0; private boolean isTaskRunning = false; // 标记当前是否有任务在执行 // 初始化Timer moveTimer = new Timer(500, e -> { // 如果有任务在执行或路径走完,直接返回 if (isTaskRunning || currentStep >= path.length) { return; } isTaskRunning = true; // 用SwingWorker执行后台耗时任务 SwingWorker<Void, Void> worker = new SwingWorker<>() { @Override protected Void doInBackground() throws Exception { // 这里放耗时的移动逻辑,比如路径校验、数据同步等 Square target = path[currentStep]; performHeavyMovementLogic(target); return null; } @Override protected void done() { // 任务完成后,更新UI,步进索引,重置标记 updateUIFromMovement(); currentStep++; isTaskRunning = false; } }; worker.execute(); });
关键注意事项
- EDT线程规则:所有Swing UI操作必须在事件调度线程(EDT)执行,
SwingWorker的done方法自动在EDT调用,适合更新UI;而doInBackground是后台线程,绝对不能在这里操作UI。 - Timer间隔设置:纯动画场景建议用30-50ms的间隔,保证流畅度;如果是分步跳转,间隔可以设成你想要的停留时间(比如500ms)。
- 路径数组安全:确保路径数组在Timer执行过程中不会被修改,否则可能出现索引越界或移动混乱的问题。
内容的提问来源于stack exchange,提问作者kattapillar




