我正在创建一个20分钟的倒数计时器应用程序。我正在使用JavaFX SceneBuilder来做到这一点。计时器由两个标签(一个为分钟,一个为秒,每个标签由CountdownTimer
类对象组成)和进度条组成(计时器看起来像this)。这些组件中的每一个都是独立的,并且同时在独立的线程上运行,以防止UI冻结。而且有效。
问题:
我需要能够暂停和恢复的三个线程(minutesThread
,secondsThread
,progressBarUpdaterThread
)是常规的.java类。当用户单击播放(开始)按钮时,单击将向FXMLDocumentController
(控制UI中组件更新方式的类)方法startTimer()
发信号,以进行有关计时器的工作。
现在startTimer()
中唯一的功能FXMLDocumentController
是:用户单击播放(开始)按钮->计时器开始倒计时。
我希望用户能够使用相同的按钮暂停和恢复计时器。我已经尝试过在FXMLDocumentController
类和其他三个线程之间使用同步,但没有以多种不同的方式使用(诚然,我几乎没有并发编码的经验)。我只想能够暂停播放计时器!
任何人都可以向我提供有关如何进行此操作的建议吗?预先感谢。
FXMLDocumentController.java中的startTimer()(用于启动倒数计时器):
@FXML
void startTimer(MouseEvent event) throws FileNotFoundException {
// update click count so user can switch between pause and start
startTimerButtonClickCount++;
// create a pause button image to replace the start button image when the user pauses the timer
Image pauseTimerButtonImage = new Image(new
FileInputStream("/Users/Home/NetBeansProjects/Take20/src/Images/pause2_black_18dp.png"));
// setting imageview to be used when user clicks on start button to pause it
ImageView pauseTimerButtonImageView = new ImageView(pauseTimerButtonImage);
// setting the width and height of the pause image
pauseTimerButtonImageView.setFitHeight(31);
pauseTimerButtonImageView.setFitWidth(28);
// preserving the pause image ratio after resize
pauseTimerButtonImageView.setPreserveRatio(true);
// create a start button image to replace the pause button image when the user unpauses the timer
Image startTimerButtonImage = new Image(new
FileInputStream("/Users/Home/NetBeansProjects/
Take20/src/Images/play_arrow2_black_18dp.png"));
ImageView startTimerButtonImageView = new ImageView(startTimerButtonImage);
startTimerButtonImageView.setFitHeight(31);
startTimerButtonImageView.setFitWidth(28);
startTimerButtonImageView.setPreserveRatio(true);
// progressBar updater
ProgressBarUpdater progressBarUpdater = new ProgressBarUpdater();
TimerThread progressBarThread = new TimerThread(progressBarUpdater);
// minutes timer
CountdownTimer minutesTimer = new CountdownTimer(19);
TimerThread minutesThread = new TimerThread(minutesTimer);
// seconds timer
CountdownTimer secondsTimer = new CountdownTimer(59);
TimerThread secondsThread = new TimerThread(secondsTimer);
// bind our components in order to update them
progressBar.progressProperty().bind(progressBarUpdater.progressProperty());
minutesTimerLabel.textProperty().bind(minutesTimer.messageProperty());
secondsTimerLabel.textProperty().bind(secondsTimer.messageProperty());
// start the threads in order to have them run parallel when the start button is clicked
progressBarThread.start();
minutesThread.start();
secondsThread.start();
// if the start button was clicked, then we set its graphic to the pause image
// if the button click count is divisible by 2, we pause it, otherwise, we play it (and change
// the button images accordingly).
if (startTimerButtonClickCount % 2 == 0) {
startTimerButton.setGraphic(pauseTimerButtonImageView);
progressBarThread.pauseThread();
minutesThread.pauseThread();
secondsThread.pauseThread();
progressBarThread.run();
minutesThread.run();
secondsThread.run();
} else {
startTimerButton.setGraphic(startTimerButtonImageView);
progressBarThread.resumeThread();
minutesThread.resumeThread();
secondsThread.resumeThread();
progressBarThread.run();
minutesThread.run();
secondsThread.run();
}
}
TimerThread(用于在用户单击UI中的播放/暂停按钮时挂起/恢复计时器线程:] >>
public class TimerThread extends Thread implements Runnable { public boolean paused = false; public final Task<Integer> timerObject; public final Thread thread; public TimerThread(Task timerObject) { this.timerObject = timerObject; this.thread = new Thread(timerObject); } @Override public void start() { this.thread.start(); System.out.println("TimerThread started"); } @Override public void run() { System.out.println("TimerThread class run() called"); try { synchronized (this.thread) { System.out.println("synchronized called"); while (paused) { System.out.println("wait called"); this.thread.wait(); System.out.println("waiting..."); } } } catch (Exception e) { System.out.println("exception caught in TimerThread"); } } synchronized void pauseThread() { paused = true; } synchronized void resumeThread() { paused = false; notify(); } }
CountdownTimer.java(用于创建和更新倒数计时器的分钟和秒):
public class CountdownTimer extends Task<Integer> { private int time; private Timer timer; private int timerDelay; private int timerPeriod; private int repetitions; public CountdownTimer(int time) { this.time = time; this.timer = new Timer(); this.repetitions = 1; } @Override protected Integer call() throws Exception { // we will create a new thread for each time unit (minutes, seconds) // we start with whatever time is passed to the constructor // we have threads devoted to each case so both minutes and second cases can run parallel to each other. switch (time) { // for our minutes timer case 19: // first display should be 19 first since our starting timer time should be 19:59 updateMessage("19"); // set delay and period to change every minute of the countdown // 60,000 milliseconds in one minute timerDelay = 60000; timerPeriod = 60000; System.out.println("Running minutesthread...."); // use a timertask to loop through time at a fixed rate as set by timerDelay, until the timer reaches 0 and is cancelled timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { //check if the flag is divisible by 2, then we sleep this thread // if time reaches 0, we want to update the minute label to 00 if (time == 0) { updateMessage("0" + Integer.toString(time)); timer.cancel(); timer.purge(); // if the time is a single digit, append a 0 and reduce time by 1 } else if (time <= 10) { --time; updateMessage("0" + Integer.toString(time)); // otherwise, we we default to reducing time by 1, every minute } else { --time; updateMessage(Integer.toString(time)); } } }, timerDelay, timerPeriod); // exit switch statement once we finish our work break; // for our seconds timer case 59: // first display 59 first since our starting timer time should be 19:59 updateMessage("59"); // use a counter to count repetitions so we can cancel the timer when it arrives at 0, after 20 repetitions // set delay and period to change every second of the countdown // 1000 milliseconds in one second timerDelay = 1000; timerPeriod = 1000; System.out.println("Running seconds thread...."); // use a timertask to loop through time at a fixed rate as set by timerDelay, until the timer reaches 0 and is cancelled timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { --time; System.out.println("repititions: " + repetitions); // Use a counter to count repetitions so we can cancel the timer when it arrives at 0, after 1200 repetitions // We will reach 1200 repetitions at the same time as the time variable reaches 0, since the timer // loops/counts down every second (1000ms). // 1200 seconds = 20 minutes * 60 seconds (1 minute) repetitions++; if (time == 0) { if (repetitions == 1200) { // reset repetitions if user decides to click play again repetitions = 0; timer.cancel(); System.out.println("repetitions ran"); } updateMessage("0" + Integer.toString(time)); // reset timer to 60, so it will countdown again from 60 after reaching 0 (since we have to repeat the seconds timer multiple times, // unlike the minutes timer, which only needs to run once time = 60; System.out.println("time == 00 ran"); } else if (time < 10 && time > 0) { updateMessage("0" + Integer.toString(time)); } else { updateMessage(Integer.toString(time)); } } }, timerDelay, timerPeriod); // exit switch statement once we finish our work break; } return null; } }
ProgressBarUpdater.java(用于在倒数计时器倒计时时更新进度条):
public class ProgressBarUpdater extends Task<Integer> {
private int progressBarPeriod;
private Timer timer;
private double time;
public ProgressBarUpdater() {
this.timer = new Timer();
this.time = 1200000;
}
@Override
protected Integer call() throws Exception {
progressBarPeriod = 10;
System.out.println("Running progressBar thread....");
// using a timer task, we update our progressBar by reducing the filled progressBar every 9.68 milliseconds
// (instead of 10s to account for any delay in program runtime) to ensure that the progressBar ends at the same time our timer reaches 0.
// according to its max (1200000ms or 20 minutes)
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
time -= 9.68;
updateProgress(time, 1200000);
System.out.println("progressBarUpdater is running");
}
}, 0, progressBarPeriod);
return null;
}
@Override
protected void updateProgress(double workDone, double maxTime) {
super.updateProgress(workDone, maxTime);
}
}
我正在创建一个20分钟的倒数计时器应用程序。我正在使用JavaFX SceneBuilder来做到这一点。计时器由两个标签组成(一个为分钟,一个为秒,每个都由CountdownTimer组成...
正如我在评论中提到的那样,为此使用后台线程,再加上三个