在 JavaFX 中同步倒数计时器的最佳方式
Best way to synchronize countdowntimers in JavaFX
我的程序需要九个倒数计时器。定时器由用户启动。在我的实现中,我为每个启动的计时器创建了一个计时器类。 timerclass 使用时间线。根据计时器的开始,秒是异步的。
我不确定如何进行。
我的第一个想法是只对所有倒计时使用一个时间轴。我会将所有 stringProperties 放入一个列表中,时间线将更改 属性。我不太确定这是不是一个好方法?
通过一些 google 我发现有一个 animationtimer 可以用来解决这样的问题。但是我无法理解这些例子。我必须覆盖句柄方法。我应该如何用这个更新我的计时器?
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);
}
}
更新
更新中,只有一个Timeline
。如果两个计数器都为 运行,则它们会同时更改。
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);
}
}
这个想法是正确的:使用一个动画工具,例如 PauseTransition
或 TimeLine
(1) 来更新所有计数器,如下所示 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) 要使用 TimeLine
而不是 PauseTransition
,请将 update()
更改为:
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();
}
我的程序需要九个倒数计时器。定时器由用户启动。在我的实现中,我为每个启动的计时器创建了一个计时器类。 timerclass 使用时间线。根据计时器的开始,秒是异步的。
我不确定如何进行。
我的第一个想法是只对所有倒计时使用一个时间轴。我会将所有 stringProperties 放入一个列表中,时间线将更改 属性。我不太确定这是不是一个好方法?
通过一些 google 我发现有一个 animationtimer 可以用来解决这样的问题。但是我无法理解这些例子。我必须覆盖句柄方法。我应该如何用这个更新我的计时器?
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);
}
}
更新
更新中,只有一个Timeline
。如果两个计数器都为 运行,则它们会同时更改。
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);
}
}
这个想法是正确的:使用一个动画工具,例如 PauseTransition
或 TimeLine
(1) 来更新所有计数器,如下所示 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) 要使用
TimeLine
而不是 PauseTransition
,请将 update()
更改为:
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();
}