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
相关产品推荐
相关产品推荐

