【问题标题】:Fast counting timer in JavaFXJavaFX中的快速计数计时器
【发布时间】:2016-01-14 22:47:38
【问题描述】:

为了效果,我想要一个标签来显示时间,就像秒表一样。时间从 0 开始,到 2 或 3 秒左右结束。需要时停止服务。我遇到的问题是因为我试图每秒更新文本 1000 次,计时器滞后。

就像我说的,这是为了效果,一旦计时器停止就会隐藏,但时间应该相当准确,而不是落后 3 秒。

有什么办法可以加快速度吗?如果可能,我希望保留所有 4 位小数。

timer = new Label();
DoubleProperty time = new SimpleDoubleProperty(0.0);
timer.textProperty().bind(Bindings.createStringBinding(() -> {
    return MessageFormat.format(gui.getResourceBundle().getString("ui.gui.derbydisplay.timer"), time.get());
}, time));

Service<Void> countUpService = new Service<Void>() {
    @Override
    protected Task<Void> createTask() {
        return new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                while (!isCancelled()) {
                    Platform.runLater(() -> time.set(time.get() + 0.0001));
                    Thread.sleep(1);
                }
                return null;
            }
        };
    }
};

【问题讨论】:

    标签: java javafx timer


    【解决方案1】:

    您的时钟滞后的主要原因是因为您每秒增加 0.0001 秒(即 1/10000 秒)1000 次。所以每秒它会增加 0.1... 但即使你解决了这个问题,它仍然会滞后一小部分,因为你没有考虑到进行方法调用所需的时间。您可以通过在启动时检查系统时钟,然后在每次更新时检查它来解决此问题。

    每秒更新标签 1000 次几乎是多余的,因为 JavaFX 的目标只是每秒更新 UI 60 次。 (如果 UI 线程忙于尝试做太多事情,它会变慢,您也可以通过安排对Platform.runLater() 的大量调用来实现这一点。)您可以使用AnimationTimer 来解决这个问题。 AnimationTimer.handle(...) 方法在每次渲染一帧时调用一次,因此这会有效地更新,因为 JavaFX 允许 UI 更新。

    AnimationTimer timer = new AnimationTimer() {
    
        private long startTime ;
    
        @Override
        public void start() {
            startTime = System.currentTimeMillis();
            super.start();
        }
    
        @Override
        public void handle(long timestamp) {
            long now = System.currentTimeMillis();
            time.set((now - startTime) / 1000.0);
        }
    };
    

    您可以使用timer.start(); 开始它,并使用timer.stop(); 停止它。显然,您可以添加更多功能来设置运行时间等,并在需要时从handle(...) 方法调用stop()

    SSCE:

    import javafx.animation.AnimationTimer;
    import javafx.application.Application;
    import javafx.beans.binding.Bindings;
    import javafx.beans.property.BooleanProperty;
    import javafx.beans.property.DoubleProperty;
    import javafx.beans.property.SimpleBooleanProperty;
    import javafx.beans.property.SimpleDoubleProperty;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.Label;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    public class Timer extends Application {
    
        @Override
        public void start(Stage primaryStage) {
    
            Label label = new Label();
            DoubleProperty time = new SimpleDoubleProperty();
            label.textProperty().bind(time.asString("%.3f seconds"));
    
            BooleanProperty running = new SimpleBooleanProperty();
    
            AnimationTimer timer = new AnimationTimer() {
    
                private long startTime ;
    
                @Override
                public void start() {
                    startTime = System.currentTimeMillis();
                    running.set(true);
                    super.start();
                }
    
                @Override
                public void stop() {
                    running.set(false);
                    super.stop();
                }
    
                @Override
                public void handle(long timestamp) {
                    long now = System.currentTimeMillis();
                    time.set((now - startTime) / 1000.0);
                }
            };
    
            Button startStop = new Button();
    
            startStop.textProperty().bind(
                Bindings.when(running)
                    .then("Stop")
                    .otherwise("Start"));
    
            startStop.setOnAction(e -> {
                if (running.get()) {
                    timer.stop();
                } else {
                    timer.start();
                }
            });
    
            VBox root = new VBox(10, label, startStop);
            root.setAlignment(Pos.CENTER);
            Scene scene = new Scene(root, 320, 120);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    传递给handle(...) 方法的值实际上是以纳秒为单位的时间戳。您可以使用它并将 startTime 设置为 System.nanoTime(),尽管当帧以每秒最多 60 帧的速度渲染时,您获得的精度远远超过实际使用的精度。

    【讨论】:

    • 这很好用,我正在研究一种模拟小数点后四位的方法。我想取第二个和第三个数字,然后将它们相乘以获得一些随机数。
    • 如果你想要第 4 位,只需使用纳米时间...在start 方法中执行startTime = System.nanoTime();,在handle 中执行time.set((timestamp - startTime) / 1_000_000_000.0);(您可以完全删除now )。 (你可以像这样最多 9 位数字,但显然它们毫无意义......)
    猜你喜欢
    • 1970-01-01
    • 2016-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-30
    • 1970-01-01
    相关资源
    最近更新 更多