问题
My Programm needs nine Countdowntimers. The timers are started by the user. In my implementation I create a timerclasses for each timer started. The timerclass uses a timeline. Depending on the start of the timers the seconds are asynchrone.
I am not sure how to proceed.
My first thought were to use only 1 timeline for all countdowns. I would put all stringProperties into a list and the timeline will change the property. I am not so sure if this is a good way?
With some google I found out that there is animationtimer which could be used for such a problem. But I couldn't understand the examples. I have to overwrite the handle method. How should I update my timer with this?
回答1:
The idea is correct: use one animation tool such as PauseTransition
or TimeLine
(1) to update all counters as demonstrated in the following MRE:
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javafx.animation.PauseTransition;
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.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class SyncedCounters extends Application {
private static final int MAX_COUNT = 100;
private Map<Label, Integer> counters;
private VBox countersPane;
@Override public void start(Stage stage) throws IOException {
counters = new HashMap<>();
countersPane = new VBox();
Button addCounter = new Button("Add Counter");
addCounter.setOnAction(e->addCounter());
BorderPane root = new BorderPane(countersPane, null, null, null, addCounter);
stage.setScene(new Scene(new ScrollPane(root),250,200));
stage.show();
update();
}
private void update() {
PauseTransition pause = new PauseTransition(Duration.seconds(1));
pause.setOnFinished(event ->{
updateCounters();
pause.play();
});
pause.play();
}
private void addCounter() {
Label label = new Label(String.valueOf(MAX_COUNT));
label.setAlignment(Pos.CENTER);
label.setPrefSize(150, 25);
counters.put(label, MAX_COUNT);
countersPane.getChildren().add(label);
}
private void updateCounters() {
for(Label l : counters.keySet()){
int counterValue = counters.get(l);
if(counterValue > 0 ){
counterValue--;
l.setText(String.valueOf(counterValue));
counters.put(l, counterValue);
}
}
}
public static void main(String[] args) {
launch(args);
}
}
(1) To use
TimeLine
instead of PauseTransition
change update()
to :
void update() {
Timeline timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
KeyFrame keyFrame = new KeyFrame(
Duration.seconds(1),
event -> {updateCounters();}
);
timeline.stop();
timeline.getKeyFrames().clear();
timeline.getKeyFrames().add(keyFrame);
timeline.play();
}
回答2:
If I understand you correctly, here is a sample program that demos `Timeline`. It uses two `Timelines` to count down to zero. **Update:** I think what you are saying is that you want the timers' numbers to change at the same time. If so update below.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
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.stage.Stage;
import javafx.util.Duration;
/**
* @author rstein
*/
public class App extends Application {
Timeline timeline1;
Timeline timeline2;
Button button1 = new Button("Start");
Button button2 = new Button("Start");
@Override
public void start(final Stage primaryStage) {
IntegerProperty counter1 = new SimpleIntegerProperty(9);
timeline1 = new Timeline(new KeyFrame(Duration.seconds(1), (t) ->
{
counter1.set(counter1.get() - 1);
if(counter1.get() == 0)
{
timeline1.stop();
System.out.println("Done!");
button1.setText("Done");
}
System.out.println("counter 1: " + counter1.get());
}));
timeline1.setCycleCount(Timeline.INDEFINITE);
button1.setOnAction((t) ->
{
switch(button1.getText())
{
case "Start":
timeline1.play();
button1.setText("Pause");
break;
case "Pause":
timeline1.stop();
button1.setText("Start");
break;
}
});
Label label1 = new Label();
label1.textProperty().bind(counter1.asString());
IntegerProperty counter2 = new SimpleIntegerProperty(9);
timeline2 = new Timeline(new KeyFrame(Duration.seconds(1), (t) ->
{
counter2.set(counter2.get() - 1);
if(counter2.get() == 0)
{
timeline2.stop();
System.out.println("Done!");
button2.setText("Done");
}
System.out.println("counter 1: " + counter1.get());
}));
timeline2.setCycleCount(Timeline.INDEFINITE);
button2.setOnAction((t) ->
{
switch(button2.getText())
{
case "Start":
timeline2.play();
button2.setText("Pause");
break;
case "Pause":
timeline2.stop();
button2.setText("Start");
break;
}
});
Label label2 = new Label();
label2.textProperty().bind(counter2.asString());
VBox root = new VBox(new HBox(button1, label1), new HBox(button2, label2));
Scene scene = new Scene(root, 300, 200);
primaryStage.setTitle(this.getClass().getSimpleName());
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(final String[] args) {
Application.launch(args);
}
}
Update
In the update, there is only one Timeline
. Both counters change at the same time if they are both running.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
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.stage.Stage;
import javafx.util.Duration;
/**
* @author Sedrick
*/
public class App extends Application {
Timeline timeline;
Button button1 = new Button("Start");
Button button2 = new Button("Start");
Boolean btn1IsRunning = false;
Boolean btn2IsRunning = false;
@Override
public void start(final Stage primaryStage) {
IntegerProperty counter1 = new SimpleIntegerProperty(9);
IntegerProperty counter2 = new SimpleIntegerProperty(9);
timeline = new Timeline(new KeyFrame(Duration.seconds(1), (t) ->
{
if(btn1IsRunning && !button1.getText().equals("Done"))
{
counter1.set(counter1.get() - 1);
}
if(counter1.get() == 0)
{
System.out.println("Done!");
button1.setText("Done");
}
System.out.println("counter 1: " + counter1.get());
if(btn2IsRunning && !button2.getText().equals("Done"))
{
counter2.set(counter2.get() - 1);
}
if(counter2.get() == 0)
{
System.out.println("Done!");
button2.setText("Done");
}
System.out.println("counter 2: " + counter2.get());
if(button1.getText().equals("Done")&& button2.getText().equals("Done"))
{
timeline.stop();
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
button1.setOnAction((t) ->
{
switch(button1.getText())
{
case "Start":
if(timeline.getStatus() != Timeline.Status.RUNNING)
{
timeline.play();
}
btn1IsRunning = true;
button1.setText("Pause");
break;
case "Pause":
btn1IsRunning = false;
button1.setText("Start");
break;
}
});
Label label1 = new Label();
label1.textProperty().bind(counter1.asString());
button2.setOnAction((t) ->
{
switch(button2.getText())
{
case "Start":
if(timeline.getStatus() != Timeline.Status.RUNNING)
{
timeline.play();
}
btn2IsRunning = true;
button2.setText("Pause");
break;
case "Pause":
btn2IsRunning = false;
button2.setText("Start");
break;
}
});
Label label2 = new Label();
label2.textProperty().bind(counter2.asString());
VBox root = new VBox(new HBox(button1, label1), new HBox(button2, label2));
Scene scene = new Scene(root, 300, 200);
primaryStage.setTitle(this.getClass().getSimpleName());
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(final String[] args) {
Application.launch(args);
}
}
来源:https://stackoverflow.com/questions/59681624/best-way-to-synchronize-countdowntimers-in-javafx