You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

沿路径移动物体:如何让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)执行,SwingWorkerdone方法自动在EDT调用,适合更新UI;而doInBackground是后台线程,绝对不能在这里操作UI。
  • Timer间隔设置:纯动画场景建议用30-50ms的间隔,保证流畅度;如果是分步跳转,间隔可以设成你想要的停留时间(比如500ms)。
  • 路径数组安全:确保路径数组在Timer执行过程中不会被修改,否则可能出现索引越界或移动混乱的问题。

内容的提问来源于stack exchange,提问作者kattapillar

火山引擎 最新活动