How to restart a timer in JavaFX? - java

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;
}
});

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.

Related

creating simple stopwatch - javaFX

I try to avoid asking for help and do as much as I can by myself. But I half been working on this for far too long. I don't even understand what is wrong. oh and when I try to test it my GUI freezes
I have to make a basic stop watch: start, stop, resume, and reset. and yet i can't do it. Please if you would also comment or explain your code, because I feel clueless. (some of my comments are past code I thought would work but didn't)
package gameclock;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class GameClock extends Application {
public void start(Stage primaryStage) {
HBox pane = new HBox();
Scene scene = new Scene(pane, 400, 100);
Button start = new Button("Start");
Button stop = new Button("Stop");
Button resume = new Button("Resume");
Button reset = new Button("Reset");
final TextField display = new TextField("0");
display.setEditable(false);
boolean onOff = false;
DateFormat df = new SimpleDateFormat("ss");
Calendar calobj = Calendar.getInstance();
System.out.println(df.format(calobj.getTime()));
start.setOnAction((ActionEvent event) ->
{
int time = Integer.parseInt(display.getText());
/*
Timer t = new Timer();
final int time1 = 1;
t.scheduleAtFixedRate(new TimerTask()
{
public void run()
{
time = time1 + 1;
}
},0,0);
display.setText(String.valueOf(time1));
*/
long timestamp = System.currentTimeMillis(); //divide by 1000 cause a millisecond is 1000 of a second
while (!onOff)
{
if(System.currentTimeMillis() - timestamp > 1000)
{
time++;
display.setText(String.valueOf(time));
}
}
String time2String = Long.toString(timestamp);
});
stop.setOnAction((ActionEvent event) ->
{
if(onOff == false)
onOff(true);
});
resume.setOnAction((ActionEvent event) ->
{
onOff(true);
});
pane.getChildren().addAll(start, display, resume, stop, reset);
pane.setAlignment(Pos.CENTER);
pane.setPadding(new Insets(0, 0, 0, 0));
primaryStage.setTitle("Stop Watch");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
private void onOff(boolean b) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
You're blocking the application thread by adding the loop directly to the event handler. This results in the UI not being updated and no further events being handled.
Better use a AnimationTimer:
AnimationTimer timer = new AnimationTimer() {
private long timestamp;
private long time = 0;
private long fraction = 0;
#Override
public void start() {
// current time adjusted by remaining time from last run
timestamp = System.currentTimeMillis() - fraction;
super.start();
}
#Override
public void stop() {
super.stop();
// save leftover time not handled with the last update
fraction = System.currentTimeMillis() - timestamp;
}
#Override
public void handle(long now) {
long newTime = System.currentTimeMillis();
if (timestamp + 1000 <= newTime) {
long deltaT = (newTime - timestamp) / 1000;
time += deltaT;
timestamp += 1000 * deltaT;
display.setText(Long.toString(time));
}
}
};
This will run the handle method on the application thread every frame.
You can use start() and stop() to start and stop the stopwatch.
I can recognize an infinite loop with the best of them:
while (!onOff)
{
if(System.currentTimeMillis() - timestamp > 1000)
{
time++;
display.setText(String.valueOf(time));
}
}
I don't know much about JavaFX as I do Java/Android, but there's nothing that's going to toggle onOff from false to true within that loop. Even if you have another button callback to toggle onOff to true, it won't get called because your UI thread is stuck in the loop above for the start button action.
Update:
Now that I see what you are trying to do: Update a text value with the current time, what you want to be using is the notion of a timer. A timer is UI concept in which you get called back in the future while still allowing your UI to be responsive. A quick google of "JavaFX timer" reveals lots of hits on how to do this.

Fast counting timer in JavaFX

For effect, I want a label to display time like a stop watch would. The time starts at 0, and ends somewhere around 2 or 3 seconds. The service is stopped when desired. The issue I am having, is because I am trying to update the text 1000 times per second, the timer is lagging behind.
Like I said, this is for effect and will be hidden as soon as the timer stops, but the time should be fairly accurate, not 3 seconds behind.
Is there any way I can make this faster? I would like to have all 4 decimal places if possible.
timer = new Label();
DoubleProperty time = new SimpleDoubleProperty(0.0);
timer.textProperty().bind(Bindings.createStringBinding(() -> {
return MessageFormat.format(gui.getResourceBundle().getString("ui.gui.derbydisplay.timer"), time.get());
}, time));
Service<Void> countUpService = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
while (!isCancelled()) {
Platform.runLater(() -> time.set(time.get() + 0.0001));
Thread.sleep(1);
}
return null;
}
};
}
};
The main reason your clock lags is because you increase the time by 0.0001 seconds (i.e. 1/10000 seconds) 1000 times per second. So every second it will increase by 0.1... But even if you fix that issue, it would still lag a small amount because you don't account for the time it takes to make the method calls. You can fix this by checking the system clock when you start, and then checking it every time you do the update.
Updating the label 1000 times per second is pretty much redundant, because JavaFX only aims to update the UI 60 times per second. (It will be slower if the UI thread is busy trying to do too much stuff, which you also make happen by scheduling so many calls to Platform.runLater().) You can fix this by using an AnimationTimer. The AnimationTimer.handle(...) method is called once every time a frame is rendered, so this effectively updates as often as JavaFX allows the UI to update.
AnimationTimer timer = new AnimationTimer() {
private long startTime ;
#Override
public void start() {
startTime = System.currentTimeMillis();
super.start();
}
#Override
public void handle(long timestamp) {
long now = System.currentTimeMillis();
time.set((now - startTime) / 1000.0);
}
};
You can start this with timer.start(); and stop it with timer.stop();. Obviously you can add more functionality to set a time for it to run, etc, and call stop() from the handle(...) method if you need.
SSCEE:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Timer extends Application {
#Override
public void start(Stage primaryStage) {
Label label = new Label();
DoubleProperty time = new SimpleDoubleProperty();
label.textProperty().bind(time.asString("%.3f seconds"));
BooleanProperty running = new SimpleBooleanProperty();
AnimationTimer timer = new AnimationTimer() {
private long startTime ;
#Override
public void start() {
startTime = System.currentTimeMillis();
running.set(true);
super.start();
}
#Override
public void stop() {
running.set(false);
super.stop();
}
#Override
public void handle(long timestamp) {
long now = System.currentTimeMillis();
time.set((now - startTime) / 1000.0);
}
};
Button startStop = new Button();
startStop.textProperty().bind(
Bindings.when(running)
.then("Stop")
.otherwise("Start"));
startStop.setOnAction(e -> {
if (running.get()) {
timer.stop();
} else {
timer.start();
}
});
VBox root = new VBox(10, label, startStop);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 320, 120);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The value passed into the handle(...) method is actually a timestamp in nanoseconds. You could use this and set the startTime to System.nanoTime(), though the precision you get there is way more than you can realistically use when the frames render at maximum of 60 frames per second.

JavaFX animation restore to original state

My goal here is to have some animation on a node (such as a fade transition) that serves as a temporary notice that something is happening. I want the animation completely gone, like it never happened when that something has ended.
The code snipped below is an example of the problem I'm having. In the current state, when the button is hit to stop the process the button just stays at it's current opacity. If the commented line is uncommented, the button no longer stays at it's current opacity but updates to look correct. My problem then is that when the button is hit again, the CSS opacity for the default stylesheet (Modena.css for JavaFX 8) is no longer taking effect.
Is there something I'm doing wrong, or is there a better way altogether?
package gui.control.custom;
import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Test extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Stage stage = new Stage();
HBox box = new HBox();
streamButton = new Button("Start");
streamButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (started) {
stopProcess();
} else {
startProcess();
}
}
});
box.getChildren().add(streamButton);
stage.setScene(new Scene(box));
stage.show();
}
FadeTransition ft;
Button streamButton;
boolean started = false;
private void startProcess() {
streamButton.setDisable(true);
new Thread() {
#Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
}
Platform.runLater(() -> {
started = true;
streamButton.setText("Stop");
streamButton.setDisable(false);
startButtonAnim();
});
}
}.start();
}
private void stopProcess() {
streamButton.setText("Start");
stopButtonAnim();
started = false;
}
private void startButtonAnim() {
ft = new FadeTransition(Duration.millis(500), streamButton);
ft.setFromValue(1.0);
ft.setToValue(0.3);
ft.setCycleCount(Animation.INDEFINITE);
ft.setAutoReverse(true);
ft.play();
}
private void stopButtonAnim() {
ft.stop();
//streamButton.setOpacity(1);
}
public static void main(String[] args) {
launch();
}
}
I think the best solution is to use jumpTo(Duration duration) right before you stop the Animation. Setting the duration to Duration.ZERO.
Circle circle2 = new Circle(250, 120, 80);
circle2.setFill(Color.RED);
circle2.setStroke(Color.BLACK);
FadeTransition fade = new FadeTransition();
fade.setDuration(Duration.millis(5000));
fade.setFromValue(10);
fade.setToValue(0.1);
fade.setCycleCount(1000);
fade.setAutoReverse(true);
fade.setNode(circle2);
fade.play();
Button btnStop = new Button("Stop");
btnStop.setOnAction((event) -> {
fade.jumpTo(Duration.ZERO);
fade.stop();
});
Another idea:
I have found this method in javadoc: getCurrentRate(),
which should give you negative result on reversing, so the code would look like this:
private void stopButtonAnim() {
while(ft.getCurrentRate>=0); //waiting till animation goes (skips if already reversing)
while(ft.getCurrentRate<=0); //and till reverse
ft.stop(); //then stop
streamButton.setOpacity(1); //make it 100% ;)
}
Maybe you have to add Thread.sleep(int) to while cycle
I would try this insetad of simply stop(); this line
setOnFinished(e->tryToStop());
And create this method as:
public void tryToStop(){
if(!started)
fm.stop();
}
stopProcess() method changes the started variable, so it will stop in this two cases:
if it is finished
AND
if it is reqested to stop
Not tested, just an idea

Javafx live thread updates

I'm working with Javafx and threads simultaneously and I constanly run into this problem where I make a button and then when the button is clicked (using event handlers) I make a for loop that changes the button to 1,2,3,4,5 and then delays for a second in the middle of each. Like a count down!
But what happens is it delays for 5 seconds and changes the text of button to 5.
The problem is I want to see it change between 1 and 5 but all I see is 5 at the end of a 5 second delay. I would assume that it changing the button text but I don't see it. I might have to to do with the .show() method in the Javafx class.
public class HewoWorld extends Application implements EventHandler<ActionEvent>
{
Thread t = new Thread();
Button butt;
boolean buttWasClicked = false;
Circle circ1 = new Circle(40, 40, 30, Color.RED);
Circle circ2 = new Circle(100, 100, 30, Color.BLUE);
Group root;
Scene scene;
Stage disStage = new Stage();
int i = 1;
public static void main(String[] args)
{
launch(args);
}
public void start(Stage stage) throws Exception
{
disStage.setTitle("tests stuffs");
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
double windh = bounds.getHeight()/2+150;//sets height of screen
double windw = bounds.getWidth()/3;//sets width of screen
Pane layout = new Pane();
butt = new Button();
butt.setText("Hello world");
root = new Group(circ1, circ2, butt);
scene = new Scene(root, 800, 400);
disStage.setWidth(windw);
disStage.setHeight(windh);
butt.setLayoutX(200);
butt.setLayoutY(200);
butt.setOnAction(this);
disStage.setScene(scene);
disStage.show();
}
public void handle(ActionEvent event)
{
if (event.getSource() == butt && buttWasClicked == false)
{
try
{
butt.setText(i+"");
t.sleep(1000);
i++;
}
catch(Exception q)
{
}
circ1 = new Circle(40, 40, 30, Color.BLACK);
circ2 = new Circle(100, 100, 30, Color.RED);
}
}
}
Why your code doesn't work
The reason your code doesn't work is that you are blocking the FX Application Thread.
Like (almost?) all UI toolkits, JavaFX is a single-threaded UI toolkit. This means that all event handlers, and all the rendering of the UI, are performed on a single thread (called the FX Application Thread).
In your code, you have an event handler that takes more than a second to run, because it pauses for a second via a call to Thread.sleep(...). While that event handler is running, the UI cannot be redrawn (because a single thread cannot do two things at once). So while the value of the button's text has changed immediately, the new value won't actually be rendered on the screen until the handle(...) method has finished running. If you had a for loop in the handle method, nothing would be rendered until the entire loop (and anything else in the method) had completed.
How to fix it
The simplest way to do what you want in JavaFX is to use a Timeline to handle the pause. The Timeline manages the threading appropriately for you:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CountingButton extends Application {
#Override
public void start(Stage primaryStage) {
Button button = new Button("Count");
Timeline timeline = new Timeline();
for (int count = 0; count <= 5 ; count++) {
final String text = Integer.toString(count);
KeyFrame frame = new KeyFrame(Duration.seconds(count), event ->
button.setText(text));
timeline.getKeyFrames().add(frame);
}
button.setOnAction(e -> timeline.play());
primaryStage.setScene(new Scene(new StackPane(button), 120, 75));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In general, for changing the appearance of the user interface at specific time points, the JavaFX Animation API (see also the tutorial) can be useful, especially Timeline and PauseTransition.
A "lower-level" way to do this would be to create a Thread yourself and pause in that thread. This is much more advanced: you need to be careful to update the UI on the FX Application Thread, not on the thread you created. You can do this with a call to Platform.runLater(...):
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class CountingButton extends Application {
#Override
public void start(Stage primaryStage) {
Button button = new Button("Start");
button.setOnAction(e -> {
Thread thread = new Thread(() -> {
for (int i = 0; i <= 5 ; i++) {
final String text = "Count: "+i ;
Platform.runLater(() -> button.setText(text));
try {
Thread.sleep(1000);
} catch (InterruptedException exc) {
exc.printStackTrace();
}
}
});
thread.start();
});
primaryStage.setScene(new Scene(new StackPane(button), 120, 75));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For more general information on threading in JavaFX, have a look at this post: Using threads to make database requests
What you have to do is to replace the thread use by the following method :
scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(
new Runnable(){
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
//Here your code to change the number by for example incrementig the value of the button
}
});
}
},
1000,
80,
TimeUnit.MILLISECONDS);
+1 if it helps :D
In this case you need a timer to run every second and increment a counter on every hit. To my knowledge, the best way to make a timer in javafx is to use a timeline. https://stackoverflow.com/a/9966213/4683264.
int i = 0;// class field
// ....
Timeline fiveSecondsWonder = new Timeline(new KeyFrame(Duration.seconds(1), event ->
button.setText(++i)));
fiveSecondsWonder.setCycleCount(5);// repeat five times
fiveSecondsWonder.play();

JavaFX - Sequential Transition - playing back and forth (step by step)

I'm struggling with making an animation showing the searchin in Binary Search Tree in JavaFX.
The goal is to make a visualization of comparing the value of tree node with possibility to:
pause and play it any time
being able to play the animation backwards (to go at least one step back),
give a user an ability to play the animation step-by-step or whole at once.
The preview of visualization
My vision was to make a series of TranslateTransitions(TT) added in one SequentialTransition(ST). If the animation is marked as "step-by-step" the each TT pauses the whole ST in their OnFinished handler. However this kinda works only for going one-way.
My question is. What is the best approach to maintain going fluent and step-by-step animation in reverse direction ?
I was thinking about:
maybe making another sequence of inverse transitions (but how to tell
it from which step to continue ?)
somehow work with rate property ? is it possible to change it while the ST is running ?
Thank you very much for your answers.
In general, you can change the rate property of an Animation while it is in progress. The idea of using a SequentialTransition is appealing, but it doesn't work as easily as you might think. The problem arises when the sequential transition is paused at the boundary between two individual transitions: you don't have any way to tell which of the individual transitions is considered the current one (i.e. the next one or the previous one). So when you try to reverse the rate and play, the sequential transition can get confused and immediately think it's at the end of the one it's trying to play.
You might be able to hack this a little by using Animation.getCurrentTime() and Animation.jumpTo(...) to "nudge" the sequential transition a tiny amount in the correct direction before starting to play any step, but I think it's probably easier just to manage the individual transitions on their own instead of using a SequentialTransition.
Here's a simple example of using this technique to move a rectangle around:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javafx.animation.Animation;
import javafx.animation.Animation.Status;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ReverseSequentialTransitionTest extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Rectangle rect = new Rectangle(50, 50, 250, 150);
rect.setFill(Color.color(.5, .5, .1));
pane.getChildren().add(rect);
TranslateTransition ttForward = new TranslateTransition(Duration.seconds(1), rect);
ttForward.setFromX(0);
ttForward.setToX(400);
TranslateTransition ttDown = new TranslateTransition(Duration.seconds(1), rect);
ttDown.setFromY(0);
ttDown.setToY(100);
TranslateTransition ttBackward = new TranslateTransition(Duration.seconds(1), rect);
ttBackward.setFromX(400);
ttBackward.setToX(0);
TranslateTransition ttUp = new TranslateTransition(Duration.seconds(1), rect);
ttUp.setFromY(100);
ttUp.setToY(0);
List<Animation> transitions = Arrays.asList(ttForward, ttDown, ttBackward, ttUp);
IntegerProperty nextTransitionIndex = new SimpleIntegerProperty();
Button playButton = new Button("Play Forward");
playButton.setOnAction(event -> {
int index = nextTransitionIndex.get();
Animation anim = transitions.get(index);
anim.setOnFinished(evt -> nextTransitionIndex.set(index+1));
anim.setRate(1);
anim.play();
});
Button reverseButton = new Button("Play backward");
reverseButton.setOnAction(event -> {
int index = nextTransitionIndex.get()-1;
Animation anim = transitions.get(index);
anim.setOnFinished(evt -> nextTransitionIndex.set(index));
anim.setRate(-1);
anim.play();
});
// This is not really part of the answer to the current question, but the
// next three statements just disable the buttons when appropriate.
// This is a binding which is true if and only if any of the transitions are
// currently running:
BooleanBinding anyPlaying = createAnyPlayingBinding(transitions);
// Disable playButton if we are at the end of the last transition, or if
// any transitions are playing:
playButton.disableProperty().bind(
nextTransitionIndex.greaterThanOrEqualTo(transitions.size())
.or(anyPlaying)
);
// Disable reverseButton if we are at the beginning of the first transition,
// or if any transitions are currently playing:
reverseButton.disableProperty().bind(
nextTransitionIndex.lessThanOrEqualTo(0)
.or(anyPlaying));
HBox controls = new HBox(5);
controls.setAlignment(Pos.CENTER);
controls.getChildren().addAll(playButton, reverseButton);
BorderPane root = new BorderPane();
root.setCenter(pane);
root.setBottom(controls);
primaryStage.setScene(new Scene(root, 800, 400));
primaryStage.show();
}
private BooleanBinding createAnyPlayingBinding(List<Animation> transitions) {
return new BooleanBinding() {
{ // Anonymous constructor
// bind to the status properties of all the transitions
// (i.e. mark this binding as invalid if any of the status properties change)
transitions.stream()
.map(Animation::statusProperty)
.forEach(this::bind);
}
#Override
protected boolean computeValue() {
// return true if any of the transitions statuses are equal to RUNNING:
return transitions.stream()
.anyMatch(anim -> anim.getStatus()==Status.RUNNING);
}
};
}
public static void main(String[] args) {
launch(args);
}
}
In JDK 7, the event handler for the playButton looks like this:
playButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
final int index = nextTransitionIndex.get();
Animation anim = transitions.get(index);
anim.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent evt) {
nextTransitionIndex.set(index + 1) ;
}
});
anim.setRate(1);
anim.play();
}
});
and similarly for reverseButton. You will need to declare a couple of things as final as well. The createAnyPlayingBinding method is something like
private BooleanBinding createAnyPlayingBinding(final List<Animation> transitions) {
return new BooleanBinding() {
{
for (Animation transition : transitions) {
this.bind(transition.statusProperty();
}
}
#Override
protected boolean computeValue() {
// return true if any of the transitions statuses are equal to RUNNING:
for (Animation anim : transitions) {
if (anim.getStatus() == Status.RUNNING) {
return true ;
}
}
return false ;
}
};
}

Categories