JavaFX中的快速计数计时器

时间:2016-01-14 22:47:38

标签: java javafx timer

为了效果,我想要一个标签来显示时间就像秒表一样。时间从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;
            }
        };
    }
};

1 个答案:

答案 0 :(得分:2)

你的时钟滞后的主要原因是你每秒1000次将时间增加0.0001秒(即1/10000秒)。所以每秒它都会增加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();停止此操作。显然,您可以添加更多功能来设置运行时间等,如果需要,可以从stop()方法调用handle(...)

SSCEE:

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帧的速度渲染时,您获得的精度远远超过您实际使用的精度。