- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在创建一个 20 分钟倒计时器应用程序。我正在使用 JavaFX SceneBuilder 来执行此操作。该计时器由两个标签(一个表示分钟,一个表示秒——每个标签由一个 CountdownTimer 类对象组成)和一个进度条(计时器看起来像 this )组成。这些组件中的每一个都是独立的,并且同时在单独的线程上运行,以防止 UI 卡住。并且它有效。
问题:
我需要能够暂停和恢复的三个线程(minutesThread
、secondsThread
、progressBarUpdaterThread
)是常规的 .java 类。当用户单击播放(开始)按钮时,该单击会向 FXMLDocumentController
(控制 UI 中组件更新方式的类)方法 startTimer()
发出信号,以执行以下操作有关计时器的工作。
目前,FXMLDocumentController
中 startTimer()
的唯一功能是:用户单击播放(开始)按钮 --> 计时器开始倒计时。
我希望用户能够使用同一个按钮暂停和恢复计时器。我尝试过在 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);
}
}
最佳答案
正如我在评论中提到的,为此使用后台线程,更不用说三个(!) 后台线程,只会使实现和推理变得更加困难。最好使用animation API由 JavaFX 提供 - 它是异步的,但仍然在 JavaFX 应用程序线程上执行。正如其他人所提到的,您只需要一个值来表示剩余时间,另一个值表示持续时间。从那里您可以显示分钟、秒和进度。
就我个人而言,我会使用 AnimationTimer
因为它为您提供当前帧的时间戳,您可以使用它来计算剩余时间。为了使事情更容易使用,我还将 AnimationTimer
包装在另一个类中,并让后一个类公开更适合倒计时器的 API。例如:
package com.example;
import java.util.concurrent.TimeUnit;
import javafx.animation.AnimationTimer;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleLongProperty;
public class CountdownTimer {
private static long toMillis(long nanos) {
return TimeUnit.NANOSECONDS.toMillis(nanos);
}
/* *********************************************************************
* *
* Instance Fields *
* *
***********************************************************************/
private final Timer timer = new Timer();
private long cachedDuration;
/* *********************************************************************
* *
* Constructors *
* *
***********************************************************************/
public CountdownTimer() {}
public CountdownTimer(long duration) {
setDuration(duration);
}
/* *********************************************************************
* *
* Public API *
* *
***********************************************************************/
public void start() {
if (getStatus() == Status.READY || getStatus() == Status.PAUSED) {
timer.start();
setStatus(Status.RUNNING);
}
}
public void pause() {
if (getStatus() == Status.RUNNING) {
timer.pause();
setStatus(Status.PAUSED);
}
}
public void stopAndReset() {
timer.stopAndReset();
setStatus(Status.READY);
}
/* *********************************************************************
* *
* Properties *
* *
***********************************************************************/
private final ReadOnlyObjectWrapper<Status> status = new ReadOnlyObjectWrapper<>(this, "status", Status.READY) {
@Override protected void invalidated() {
if (get() == Status.READY) {
cachedDuration = Math.abs(getDuration());
setTimeRemaining(cachedDuration);
}
}
};
private void setStatus(Status status) { this.status.set(status); }
public final Status getStatus() { return status.get(); }
public final ReadOnlyObjectProperty<Status> statusProperty() { return status.getReadOnlyProperty(); }
private final LongProperty duration = new SimpleLongProperty(this, "duration") {
@Override protected void invalidated() {
if (getStatus() == Status.READY) {
cachedDuration = Math.abs(get());
setTimeRemaining(cachedDuration);
}
}
};
public final void setDuration(long duration) { this.duration.set(duration); }
public final long getDuration() { return duration.get(); }
public final LongProperty durationProperty() { return duration; }
private final ReadOnlyLongWrapper timeRemaining = new ReadOnlyLongWrapper(this, "timeRemaining") {
@Override protected void invalidated() {
setProgress((double) (cachedDuration - get()) / (double) cachedDuration);
}
};
private void setTimeRemaining(long timeRemaining) { this.timeRemaining.set(timeRemaining); }
public final long getTimeRemaining() { return timeRemaining.get(); }
public final ReadOnlyLongProperty timeRemainingProperty() { return timeRemaining.getReadOnlyProperty(); }
private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress");
private void setProgress(double progress) { this.progress.set(progress); }
public final double getProgress() { return progress.get(); }
public final ReadOnlyDoubleProperty progressProperty() { return progress.getReadOnlyProperty(); }
/* *********************************************************************
* *
* Static Classes *
* *
***********************************************************************/
public enum Status {
READY,
RUNNING,
PAUSED,
FINISHED
}
/* *********************************************************************
* *
* Classes *
* *
***********************************************************************/
private class Timer extends AnimationTimer {
private long triggerTime = Long.MIN_VALUE;
private long pauseTime = Long.MIN_VALUE;
private boolean pausing;
@Override
public void handle(long now) {
if (pausing) {
pauseTime = toMillis(now);
pausing = false;
stop();
} else {
if (triggerTime == Long.MIN_VALUE) {
triggerTime = toMillis(now) + cachedDuration;
} else if (pauseTime != Long.MIN_VALUE) {
triggerTime += toMillis(now) - pauseTime;
pauseTime = Long.MIN_VALUE;
}
long timeRemaining = Math.max(0, triggerTime - toMillis(now));
setTimeRemaining(timeRemaining);
if (timeRemaining == 0) {
setStatus(Status.FINISHED);
stop();
}
}
}
@Override
public void start() {
pausing = false;
super.start();
}
void pause() {
if (triggerTime != Long.MIN_VALUE) {
pausing = true;
} else {
stop();
}
}
void stopAndReset() {
stop();
triggerTime = Long.MIN_VALUE;
pauseTime = Long.MIN_VALUE;
pausing = false;
}
}
}
警告:当AnimationTimer
运行时,CountdownTimer
实例无法被垃圾回收。
此实现将持续时间和剩余时间值解释为毫秒。此外,启动计时器后更改持续时间只有在重置计时器(即调用 stopAndReset()
)后才会生效。
下面是在基于 FXML 的应用程序中使用上述 CountdownTimer
的示例。请注意,该示例使用不同的按钮来启动、暂停、恢复和重置计时器。这与您在问题中描述的不同,但您应该能够重新设计以满足您的需求。此外,该示例还提供了一种切换是否显示当前秒的毫秒的方法。
应用程序.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.CheckBox?>
<?import com.example.CountdownTimer?>
<?import com.example.CountdownTimer.Status?>
<VBox xmlns="http://javafx.com/javafx/14.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.example.Controller" prefHeight="300" prefWidth="500">
<fx:define>
<!-- 90,000ms == 1m 30s -->
<CountdownTimer fx:id="timer" duration="90000"/>
<CountdownTimer.Status fx:id="READY" fx:value="READY"/>
<CountdownTimer.Status fx:id="RUNNING" fx:value="RUNNING"/>
<CountdownTimer.Status fx:id="PAUSED" fx:value="PAUSED"/>
</fx:define>
<ToolBar style="-fx-font: 10pt 'Monospaced';">
<Button text="Start" disable="${timer.status != READY}" focusTraversable="false"
onAction="#handleStartOrResumeTimer"/>
<Button text="Resume" disable="${timer.status != PAUSED}" focusTraversable="false"
onAction="#handleStartOrResumeTimer"/>
<Button text="Pause" disable="${timer.status != RUNNING}" focusTraversable="false" onAction="#handlePauseTimer"/>
<Button text="Reset" disable="${timer.status == READY || timer.status == RUNNING}" focusTraversable="false"
onAction="#handleResetTimer"/>
<Separator/>
<CheckBox fx:id="showMillisBox" text="Show Millis" focusTraversable="false"/>
</ToolBar>
<ProgressBar progress="${timer.progress}" maxWidth="Infinity"/>
<StackPane VBox.vgrow="ALWAYS">
<Label fx:id="timerLabel" style="-fx-font: bold 48pt 'Monospaced';"/>
</StackPane>
</VBox>
Controller .java:
package com.example;
import java.time.Duration;
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
public class Controller {
@FXML private CountdownTimer timer;
@FXML private CheckBox showMillisBox;
@FXML private Label timerLabel;
@FXML
private void initialize() {
timerLabel
.textProperty()
.bind(
Bindings.createStringBinding(
this::formatTimeRemaining,
timer.timeRemainingProperty(),
showMillisBox.selectedProperty()));
timerLabel
.textFillProperty()
.bind(
Bindings.when(timer.statusProperty().isEqualTo(CountdownTimer.Status.FINISHED))
.then(Color.FIREBRICK)
.otherwise(Color.FORESTGREEN));
}
private String formatTimeRemaining() {
Duration d = Duration.ofMillis(timer.getTimeRemaining());
if (showMillisBox.isSelected()) {
return String.format("%02d:%02d:%03d", d.toMinutes(), d.toSecondsPart(), d.toMillisPart());
}
return String.format("%02d:%02d", d.toMinutes(), d.toSecondsPart());
}
@FXML
private void handleStartOrResumeTimer(ActionEvent event) {
event.consume();
timer.start();
}
@FXML
private void handlePauseTimer(ActionEvent event) {
event.consume();
timer.pause();
}
@FXML
private void handleResetTimer(ActionEvent event) {
event.consume();
timer.stopAndReset();
}
}
Main.java:
package com.example;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/com/example/App.fxml"));
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Countdown Timer Example");
primaryStage.show();
}
}
关于java - 如何根据用户的请求暂停和恢复多个 Java 线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61313461/
我正在编写一个具有以下签名的 Java 方法。 void Logger(Method method, Object[] args); 如果一个方法(例如 ABC() )调用此方法 Logger,它应该
我是 Java 新手。 我的问题是我的 Java 程序找不到我试图用作的图像文件一个 JButton。 (目前这段代码什么也没做,因为我只是得到了想要的外观第一的)。这是我的主课 代码: packag
好的,今天我在接受采访,我已经编写 Java 代码多年了。采访中说“Java 垃圾收集是一个棘手的问题,我有几个 friend 一直在努力弄清楚。你在这方面做得怎么样?”。她是想骗我吗?还是我的一生都
我的 friend 给了我一个谜语让我解开。它是这样的: There are 100 people. Each one of them, in his turn, does the following
如果我将使用 Java 5 代码的应用程序编译成字节码,生成的 .class 文件是否能够在 Java 1.4 下运行? 如果后者可以工作并且我正在尝试在我的 Java 1.4 应用程序中使用 Jav
有关于why Java doesn't support unsigned types的问题以及一些关于处理无符号类型的问题。我做了一些搜索,似乎 Scala 也不支持无符号数据类型。限制是Java和S
我只是想知道在一个 java 版本中生成的字节码是否可以在其他 java 版本上运行 最佳答案 通常,字节码无需修改即可在 较新 版本的 Java 上运行。它不会在旧版本上运行,除非您使用特殊参数 (
我有一个关于在命令提示符下执行 java 程序的基本问题。 在某些机器上我们需要指定 -cp 。 (类路径)同时执行java程序 (test为java文件名与.class文件存在于同一目录下) jav
我已经阅读 StackOverflow 有一段时间了,现在我才鼓起勇气提出问题。我今年 20 岁,目前在我的家乡(罗马尼亚克卢日-纳波卡)就读 IT 大学。足以介绍:D。 基本上,我有一家提供簿记应用
我有 public JSONObject parseXML(String xml) { JSONObject jsonObject = XML.toJSONObject(xml); r
我已经在 Java 中实现了带有动态类型的简单解释语言。不幸的是我遇到了以下问题。测试时如下代码: def main() { def ks = Map[[1, 2]].keySet()
一直提示输入 1 到 10 的数字 - 结果应将 st、rd、th 和 nd 添加到数字中。编写一个程序,提示用户输入 1 到 10 之间的任意整数,然后以序数形式显示该整数并附加后缀。 public
我有这个 DownloadFile.java 并按预期下载该文件: import java.io.*; import java.net.URL; public class DownloadFile {
我想在 GUI 上添加延迟。我放置了 2 个 for 循环,然后重新绘制了一个标签,但这 2 个 for 循环一个接一个地执行,并且标签被重新绘制到最后一个。 我能做什么? for(int i=0;
我正在对对象 Student 的列表项进行一些测试,但是我更喜欢在 java 类对象中创建硬编码列表,然后从那里提取数据,而不是连接到数据库并在结果集中选择记录。然而,自从我这样做以来已经很长时间了,
我知道对象创建分为三个部分: 声明 实例化 初始化 classA{} classB extends classA{} classA obj = new classB(1,1); 实例化 它必须使用
我有兴趣使用 GPRS 构建车辆跟踪系统。但是,我有一些问题要问以前做过此操作的人: GPRS 是最好的技术吗?人们意识到任何问题吗? 我计划使用 Java/Java EE - 有更好的技术吗? 如果
我可以通过递归方法反转数组,例如:数组={1,2,3,4,5} 数组结果={5,4,3,2,1}但我的结果是相同的数组,我不知道为什么,请帮助我。 public class Recursion { p
有这样的标准方式吗? 包括 Java源代码-测试代码- Ant 或 Maven联合单元持续集成(可能是巡航控制)ClearCase 版本控制工具部署到应用服务器 最后我希望有一个自动构建和集成环境。
我什至不知道这是否可能,我非常怀疑它是否可能,但如果可以,您能告诉我怎么做吗?我只是想知道如何从打印机打印一些文本。 有什么想法吗? 最佳答案 这里有更简单的事情。 import javax.swin
我是一名优秀的程序员,十分优秀!