提问人:MsLaus 提问时间:11/8/2023 最后编辑:MsLaus 更新时间:11/8/2023 访问量:80
倒数计时器 Java JavaFX
Countdown timer Java JavaFX
问:
有人可以帮我在 JavaFX 中制作倒数计时器吗?我需要从标签中获取时间(时间由滑块设置),并且我想查看每秒剩余的分钟和秒数。按下按钮时应开始倒计时。我搜索了 Stack Overflow,并尝试使用 ChatGPT 来做到这一点,但我仍然有问题。
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL;
import java.util.*;
public class MainController implements Initializable{
@FXML
private Button button;
@FXML
private Slider slider;
@FXML
private Label timerLabel;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
//the initial value of the label is set with the slider
slider.valueProperty().addListener((ObservableValue<? extends Number> num, Number oldVal, Number newVal) -> {
Integer value = Integer.valueOf(String.format("%.0f", newVal));
timeLabel.setText(String.valueOf(value));
});
}
//the action of the button
@FXML
private void start(){
//i tried another way and still does not work
timer = seconds;
Timer timerA = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
if(timer >0){
timeLabel.setText(String.valueOf(timer));
timer--;
}
if(timer == -1){
timeLabel.setText(String.valueOf(timer));
timerA.cancel();
}
}
};
timerA.schedule(task,0,1000);
}
}
答:
消息“时间线可能未初始化”表示您正在尝试在构造它之前使用。您正在调用一个事件处理程序,该处理程序正在传递给 Timeline 构造函数;由于构造函数尚未完成,因此编译器不能保证在事件处理程序运行时实际上已为其分配了值。换句话说,可能被初始化。timeline
timeline.stop()
timeline
timeline
解决此问题的一种方法是单独添加关键帧。这样,Timeline 构造函数就完全完成了:
Timeline timeline = new Timeline();
// Construction complete; now timeline can be used in an event handler.
timeline.getKeyFrames().add(new KeyFrame(Duration.seconds(1), event -> {
// Decrease one second
if (seconds > 0) {
seconds--;
} else {
// If seconds reach zero, decrement minutes and set seconds to 59
if (minutes > 0) {
minutes--;
seconds = 59;
} else {
// If minutes reach zero, decrement hours and set minutes and seconds to 59
if (hours > 0) {
hours--;
minutes = 59;
seconds = 59;
} else {
// If hours, minutes, and seconds reach zero, stop the timer
hours = 0;
minutes = 0;
seconds = 0;
timeline.stop();
}
}
}
// Update the label with the new values
timerLabel.setText(String.format("Time Left: %02d:%02d:%02d", hours, minutes, seconds));
}));
另一种方法是声明为私有字段,而不是局部变量。这是有效的,因为一旦封闭类完成了自己的构造函数,就可以保证每个字段都被初始化。(如果您的代码从未为某个字段赋值,则该字段将自动初始化为 null。timeline
评论
眼前的问题(“时间线可能未初始化”)由 @VGR 的回答回答。但是,使用这样的方法根本不是创建倒数计时器的方法。问题在于时间线不能保证每秒调用一次事件处理程序;它只会在呈现 UI 时执行,如果至少经过了一秒钟。Timeline
当倒计时开始时,您应该计算它应该结束的时间。然后通过计算距离结束时间的剩余时间来“定期”更新标签。您可以使用时间轴或(每次渲染 UI 时都会调用其方法)来执行此部分。这将增加一些额外的计算时间,但代价是尽可能精确 JavaFX 系统;在这里,我认为计算足够小,这不是问题,并且更喜欢这种方法。AnimationTimer
handle()
AnimationTimer
以下是使用此方法的方法:startComputation()
import javafx.animation.Animation;
import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.time.Duration;
import java.time.LocalTime;
public class CountdownTimer extends Application {
private int hours;
private int minutes;
private int seconds;
private Label timerLabel = new Label();
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
TextField hoursField = new TextField();
hoursField.setPromptText("Hours");
TextField minutesField = new TextField();
minutesField.setPromptText("Minutes");
TextField secondsField = new TextField();
secondsField.setPromptText("Seconds");
Button startButton = new Button("Start Timer");
startButton.setOnAction(event -> {
// Parse user input for hours, minutes, and seconds
hours = Integer.parseInt(hoursField.getText());
minutes = Integer.parseInt(minutesField.getText());
seconds = Integer.parseInt(secondsField.getText());
// Start the countdown timer
startCountdown();
});
root.getChildren().addAll(hoursField, minutesField, secondsField, startButton, timerLabel);
Scene scene = new Scene(root, 300, 200);
primaryStage.setTitle("Countdown Timer");
primaryStage.setScene(scene);
primaryStage.show();
}
private void startCountdown() {
LocalTime end = LocalTime.now()
.plusHours(hours)
.plusMinutes(minutes)
.plusSeconds(seconds);
AnimationTimer timer = new AnimationTimer() {
@Override
public void handle(long l) {
Duration remaining = Duration.between(LocalTime.now(), end);
if (remaining.isPositive()) {
timerLabel.setText(format(remaining));
} else {
timerLabel.setText(format(Duration.ZERO));
stop();
}
}
private String format(Duration remaining) {
return String.format("%02d:%02d:%02d",
remaining.toHoursPart(),
remaining.toMinutesPart(),
remaining.toSecondsPart()
);
}
};
timer.start();
}
}
如果您更喜欢 ,请将该方法替换为Timeline
startCountdown()
private void startCountdown() {
LocalTime end = LocalTime.now()
.plusHours(hours)
.plusMinutes(minutes)
.plusSeconds(seconds);
Timeline timeline = new Timeline();
timeline.getKeyFrames().add(new KeyFrame(Duration.seconds(1), e -> {
java.time.Duration remaining = java.time.Duration.between(LocalTime.now(), end);
if (remaining.isPositive()) {
timerLabel.setText(format(remaining));
} else {
timerLabel.setText(format(java.time.Duration.ZERO));
timeline.stop();
}
}));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}
private String format(java.time.Duration remaining) {
return String.format("%02d:%02d:%02d",
remaining.toHoursPart(),
remaining.toMinutesPart(),
remaining.toSecondsPart()
);
}
评论
Duration.isPositive()
评论
LocalTime startTime = LocalTime.now()
LocalTime endTime = startTime.plusHours(...).plusMinutes(...).plusSeconds(...);
AnimationTimer