user3326720 user3326720 - 4 months ago 45
Java Question

How to get JavaFX Timeline to increase by second and to bind to a Progressbar?

I'm attempting to create a system in which a timeline is used to record the duration of a minute. In this situation, I'd like for the timeline to increment per second and have the Progressbar record this up til 1 minute before invoking an action and resetting the timeline.

final Timeline timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
timeline.getKeyFrames().add(new KeyFrame(Duration.seconds(1)));
progress.progressProperty().bind(timeline.getTotalDuration()???);
timeline.play();


I've set the cycle count to indefinite so that the timeline cycle never stops. I've also tried binding the Progressbar property to the timeline, but it requires an Observablevalue to do so, which
timeline#getTotalDuration
doesn't offer.

I've tried running the program without the binding on a seperate thread:

new Thread(new Task<Void>() {
@Override
protected Void call() throws Exception {
while(true){
System.out.printf("s:\t%f\n",timeline.getTotalDuration().toSeconds());
Thread.sleep(1000);
}
}
}).start();


However, running this doesn't increase the timer per second, but instead just goes through all the cycles at once. Since the cycleCount I set was
INDEFINITE
, the result of
#totalDuration
is
infinite
.

How would I go about getting the timeline to work on a second-by-second basis and how would I be able to bind the duration to the Progressbar until it reaches 100% so that my special action can be invoked?

Answer

If you just want the progress bar to increase repeatedly from zero to full over the course of a minute, and execute code when it reaches the end of each minute, all you need is:

    ProgressBar progress = new ProgressBar();
    Timeline timeline = new Timeline(
        new KeyFrame(Duration.ZERO, new KeyValue(progress.progressProperty(), 0)),
        new KeyFrame(Duration.minutes(1), e-> {
            // do anything you need here on completion...
            System.out.println("Minute over");
        }, new KeyValue(progress.progressProperty(), 1))    
    );
    timeline.setCycleCount(Animation.INDEFINITE);
    timeline.play();

That will create an "analog" effect for the progress bar, i.e. it won't increase incrementally every second, but smoothly over the whole minute.

If you really want to increase incrementally each second, use an IntegerProperty to represent the number of seconds, and bind the progress bar's progress property to it:

    ProgressBar progress = new ProgressBar();
    IntegerProperty seconds = new SimpleIntegerProperty();
    progress.progressProperty().bind(seconds.divide(60.0));
    Timeline timeline = new Timeline(
        new KeyFrame(Duration.ZERO, new KeyValue(seconds, 0)),
        new KeyFrame(Duration.minutes(1), e-> {
            // do anything you need here on completion...
            System.out.println("Minute over");
        }, new KeyValue(seconds, 60))   
    );
    timeline.setCycleCount(Animation.INDEFINITE);
    timeline.play();

The point here is that the IntegerProperty will interpolate between 0 and 60, but will only accept integer values (i.e. it will truncate the interpolated value to an int).

Here is an SSCCE with the second version:

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;

public class OneMinuteTimer extends Application {

    @Override
    public void start(Stage primaryStage) {
        ProgressBar progress = new ProgressBar();
        progress.setMinWidth(200);
        progress.setMaxWidth(Double.MAX_VALUE);
        IntegerProperty seconds = new SimpleIntegerProperty();
        progress.progressProperty().bind(seconds.divide(60.0));
        Timeline timeline = new Timeline(
            new KeyFrame(Duration.ZERO, new KeyValue(seconds, 0)),
            new KeyFrame(Duration.minutes(1), e-> {
                // do anything you need here on completion...
                System.out.println("Minute over");
            }, new KeyValue(seconds, 60))   
        );
        timeline.setCycleCount(Animation.INDEFINITE);
        timeline.play();

        StackPane root = new StackPane(progress);
        root.setPadding(new Insets(20));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Comments