You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX双Timeline共存问题:秒表无法停止的解决方法

问题根源分析

你的秒表停不下来且不断重启的核心问题在于:时钟的主Timeline每秒调用一次setCurrentTime(),而setCurrentTime()又会触发paintClock()方法,但你把秒表的Timeline、按钮、文本组件的创建逻辑都写在了paintClock()里。这就意味着:

  • 每秒都会生成一个全新的秒表Timeline、按钮和文本元素
  • 旧的秒表Timeline没有被停止,仍然在后台运行
  • 你点击Stop按钮时,只能控制最新创建的那个Timeline,旧的还在继续跑,所以看起来秒表停不下来还不断重启
修复方案

我们需要把秒表相关的逻辑和时钟绘制逻辑彻底分离,确保秒表组件只初始化一次,不会被时钟的刷新操作反复重建。

步骤1:调整ClockPane的变量与初始化逻辑

把秒表的Timeline、按钮、文本等组件从paintClock()中移出来,放到单独的初始化方法里,只执行一次:

public class ClockPane extends Pane {
    private int hour;
    private int minute;
    private int second;
    Button sButton, rButton;
    Text text;
    Timeline timer; // 升级为实例变量,确保全局唯一
    int mins = 0, secs = 0, millis = 0;
    boolean sos = true;
    // 用于装时钟元素的容器,避免每次刷新重叠
    private Group clockContent;

    /** Construct a default clock with the current time */
    public ClockPane() {
        setCurrentTime();
        initClockContentContainer();
        initStopwatch(); // 初始化秒表组件
    }

    /** Construct a clock with specified hour, minute, and second */
    public ClockPane(int hour, int minute, int second) {
        this.hour = hour;
        this.minute = minute;
        this.second = second;
        initClockContentContainer();
        initStopwatch();
    }

    // 初始化时钟内容容器,避免每次刷新重叠
    private void initClockContentContainer() {
        clockContent = new Group();
        getChildren().add(clockContent);
    }

    // 单独初始化秒表组件,只执行一次
    private void initStopwatch() {
        text = new Text("00:00:000");
        text.setStyle("-fx-font-size: 11pt;-fx-stroke:silver;");
        text.setLayoutX(125);
        text.setLayoutY(180);

        // 创建唯一的秒表Timeline
        timer = new Timeline(new KeyFrame(Duration.millis(10), new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                change(text);
            }
        }));
        timer.setCycleCount(Timeline.INDEFINITE);
        timer.setAutoReverse(false);

        // 初始化Start/Stop按钮
        sButton = new Button("Start");
        sButton.setLayoutX(12);
        sButton.setLayoutY(272);
        sButton.setStyle("-fx-font-size: 9pt;-fx-text-fill:#50f441;-fx-font-weight: bold;" +
                "-fx-background-color:radial-gradient(radius 245%, black, derive(#50f441, -30%)); ");
        sButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                if(sos) {
                    timer.play();
                    sos = false;
                    sButton.setText("Stop");
                } else {
                    timer.pause();
                    sos = true;
                    sButton.setText("Start");
                }
            }
        });

        // 初始化Reset按钮
        rButton = new Button("Reset");
        rButton.setLayoutX(252);
        rButton.setLayoutY(272);
        rButton.setStyle("-fx-font-size: 9pt;-fx-text-fill:#50f441;-fx-font-weight: bold;" +
                "-fx-background-color:radial-gradient(radius 245%, black, derive(#50f441, -30%)); ");
        rButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                mins = 0;
                secs = 0;
                millis = 0;
                timer.pause();
                text.setText("00:00:000");
                if(!sos) {
                    sos = true;
                    sButton.setText("Start");
                }
            }
        });

        // 将秒表组件添加到Pane中
        getChildren().addAll(text, sButton, rButton);
    }

    // 修正change方法,适配10ms间隔的计时
    void change(Text text) {
        millis += 10;
        if(millis >= 1000) {
            secs++;
            millis = 0;
        }
        if(secs == 60) {
            mins++;
            secs = 0;
        }
        text.setText(
                String.format("%02d:%02d:%03d", mins, secs, millis)
        );
    }

步骤2:修改paintClock方法,只负责时钟绘制

把paintClock()里的秒表相关代码全部移除,只保留时钟表盘、指针、数字的绘制逻辑,并且每次刷新前清空时钟容器,避免组件重叠:

/** Paint the clock */
private void paintClock() {
    // 清空之前的时钟内容,避免重叠
    clockContent.getChildren().clear();

    // Initialize clock parameters
    double clockRadius = Math.min(getWidth(), getHeight()) * 0.55 * 0.82;
    double centerX = getWidth() / 2;
    double centerY = getHeight() / 2;

    // Draw circle
    Circle circle = new Circle(centerX, centerY, clockRadius);
    circle.setStyle(" -fx-fill: radial-gradient(radius 245%, black, derive(GREEN, -30%)); " +
            "-fx-stroke: radial-gradient(radius 180%, darkgreen, derive(#50f441, -40%),derive(#50f441, -30%));-fx-stroke-width:10;");

    // 这里补充你原来的数字、指针绘制代码...
    Group numbers = new Group();
    numbers.setLayoutX(142);
    // ... 你的数字、指针逻辑

    // 将所有时钟元素添加到容器中
    clockContent.getChildren().addAll(circle, numbers /* 指针等其他组件 */);
}
额外优化建议
  • 把秒表的Timeline间隔从1ms改成10ms:系统很难精确处理1ms的间隔,会浪费大量性能,10ms的间隔既足够流畅,又能减少资源消耗。
  • 使用String.format()简化时间格式化代码,比手动拼接更简洁可靠。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:52:13