Answering this question is easiest through an example I believe. So I've created a small Traffic Light application, since it allows me to use Circle
and a timed sequence similar to your problem, whilst being a familiar concept for all.
I'll be using java.util.Timer
alongside java.util.TimerTask
for handling the sequence of lights. You may choose to use some animation / time line in JavaFX, but I think that is overkill for this kind of task.
I include the three files used in this project:
FXMLTrafficLight.fxml
- which defines my FXML layout
FXMLTrafficLightController.java
- my FXML controller
TrafficLightApplication.java
- for completeness my subclass of Application
, this is just the boiler plate.
FXMLTrafficLight.fxml
Not a fancy layout, just a VBox
with three circles redLight
, amberLight
and greenLight
, plus two Button
objects startLights
and stopLights
used to start and stop the timer.
<VBox fx:id="root" id="VBox" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxtimer.FXMLTrafficLightController">
<children>
<Circle fx:id="redLight" radius="100"></Circle>
<Circle fx:id="amberLight" radius="100"></Circle>
<Circle fx:id="greenLight" radius="100"></Circle>
<Button fx:id="startLights" text="Start Lights" onAction="#startLights"></Button>
<Button fx:id="stopLights" text="Start Lights" onAction="#stopLights"></Button>
</children>
</VBox>
FXMLTrafficLightController.java
I've included the model/state in the controller for simplicity. Whether a light is red / amber / green is determined by a boolean
flag. The initial state is set in the initialize()
method, and is updated by calling updateState()
.
When startLights(ActionEvent)
is invoked (the EventHandler
for startLights
) a new Timer
is constructed with a TimerTask
implementation that first invokes updateState()
on the thread created by the Timer
and then invokes updateLights()
which changes the color of the lights based on the current state on the JavaFX Application Thread using Platform.runLater(Runnable)
.
Note: the TimerTask
itself will not be run on the JavaFX Application Thread, hence the need to use Platform.runLater(Runnable)
for updating the GUI.
When stopLights(ActionEvent)
is invoked, it will cancel the Timer
.
Note that both startLights(ActionEvent)
and stopLights(ActionEvent)
toggle which Button
objects are enabled on the interface as well.
public class FXMLTrafficLightController implements Initializable {
@FXML
private Circle redLight;
@FXML
private Circle amberLight;
@FXML
private Circle greenLight;
@FXML
private Button startLights;
@FXML
private Button stopLights;
private Timer timer;
private static final int DELAY = 2000; // ms
private boolean red, amber, green;
@Override
public void initialize(URL url, ResourceBundle rb) {
red = true;
amber = false;
green = false;
stopLights.setDisable(true);
updateLights();
}
@FXML
private void startLights(ActionEvent e) {
toggleButtons();
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
// Not run on the JavaFX Application Thread!
updateState();
// Using Platform.runLater(Runnable) to ensure updateLights()
// is run on the JavaFX Application Thread
Platform.runLater(new Runnable() {
@Override
public void run() {
updateLights();
}
});
}
}, 0, DELAY); // no initial delay, trigger again every 2000 ms (DELAY)
}
@FXML
private void stopLights(ActionEvent e) {
toggleButtons();
timer.cancel();
}
private void toggleButtons() {
startLights.setDisable(!startLights.isDisable());
stopLights.setDisable(!stopLights.isDisable());
}
private void updateState() {
if (red && !amber && !green) {
amber = true;
} else if (red && amber && !green) {
red = false;
amber = false;
green = true;
} else if (!red && !amber && green) {
green = false;
amber = true;
} else {
red = true;
amber = false;
green = false;
}
}
private void updateLights() {
redLight.setFill(red ? Color.RED : Color.GREY);
amberLight.setFill(amber ? Color.ORANGE : Color.GREY);
greenLight.setFill(green ? Color.GREEN : Color.GREY);
}
}
TrafficLightApplication.java
For completeness... Just the standard boiler plate with file names changed.
public class TrafficLightApplication extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLTrafficLight.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}