How to restart a timer in JavaFX?

Tags: ,



I’m currently working on a program in which users can create their on time intervals for different exercises. Once start is pressed, the countdown begins for the first exercise. Once it is done, a sound is played and countdown begins for the second one and so on until all the exercises are done and removed. I use a timer which after every 1 second, subtracts the time of the exercise by 1. The problem is, I can’t seem to find a way to restart Timers in java. When all exercises are done I can stop the timer but I can’t seem to find a way to restart it for when I want to create new exercises and go through the process again. I can’t also find a way to pause and play the timer again during a particular process. I’m new to JavaFX, so I would really appreciate if you could guide me how I can change my code to achieve what I’m looking for.

Timer timer = new Timer();
startButton.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        //Timer task=new TimerTask();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                running=true;
                if (running==true)
                {
                    if (workoutsList.size() == 0) {
                        return;
                    }
                    if (workoutsList.size() == 1 && workoutsList.get(0).time == 1) {
                        text.setText("over!");
                        mediaPlayer1.play();
                        workoutsList.clear();
                        workouts.getItems().clear();
                        timer.cancel();
                        return;
                    }
                    workoutsList.get(0).time -= 1;
                    if (workoutsList.get(0).time == 0) {
                        workoutsList.remove(0);
                        mediaPlayer.play();
                        return;
                    }
                    workouts.getItems().clear();
                    workouts.refresh();
                    for (int i = 0; i < workoutsList.size(); i++) {
                        workouts.getItems().add(workoutsList.get(i));
                    }
                }
            }
        }, 0, 1000);
    }
});
stopButton.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        timer.cancel();
        running=false;
    }
});

Answer

Since the Timer does nothing except track time it would be better to use the javafx.animation API. This gives you certain advantages:

  • Everything happens on the JavaFX Application Thread, meaning no concurrency issues.
  • You can make use of the currentTime and cycleDuration properties of Animation to track the time left in the countdown.
  • You can make use of the play(), pause(), and stop() methods of Animation to control the timer.
  • You can use the onFinished property of Animation to play you sound when the timer completes.

Here’s an example using PauseTransition, though you could also use e.g. Timeline.

import javafx.animation.Animation;
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Main extends Application {

  @Override
  public void start(Stage primaryStage) {
    // javafx.util.Duration
    PauseTransition timer = new PauseTransition(Duration.minutes(5));
    // timer.setOnFinished(e -> /* play sound */);

    Button startBtn = new Button("Start");
    startBtn.setOnAction(e -> timer.play());

    Button pauseBtn = new Button("Pause");
    pauseBtn.setOnAction(e -> timer.pause());

    Button resetBtn = new Button("Reset");
    resetBtn.setOnAction(e -> timer.stop());

    Label label = new Label();
    label.setFont(Font.font("Monospaced", 20));
    label.textProperty().bind(timeLeftAsString(timer));

    HBox hbox = new HBox(10, startBtn, pauseBtn, resetBtn);
    hbox.setAlignment(Pos.CENTER);

    VBox root = new VBox(25, label, hbox);
    root.setPadding(new Insets(25));
    root.setAlignment(Pos.CENTER);

    primaryStage.setScene(new Scene(root));
    primaryStage.show();
  }

  private StringBinding timeLeftAsString(Animation animation) {
    return Bindings.createStringBinding(
        () -> {
          double currentTime = animation.getCurrentTime().toMillis();
          double totalTime = animation.getCycleDuration().toMillis();
          long remainingTime = Math.round(totalTime - currentTime);
          // java.time.Duration
          java.time.Duration dur = java.time.Duration.ofMillis(remainingTime);
          return String.format(
              "%02d:%02d:%03d", dur.toMinutes(), dur.toSecondsPart(), dur.toMillisPart());
        },
        animation.currentTimeProperty(),
        animation.cycleDurationProperty());
  }
}

Side note: You mention a sound is played when the timer completes, and I can see a call to mediaPlayer.play(). Considering the nature of the program I assume the sound being played is relatively short. If that’s the case you should consider using AudioClip instead of MediaPlayer.



Source: stackoverflow