Создание повторяющегося действия в GUI без «дрейфа времени» - PullRequest
3 голосов
/ 06 января 2020

На основе Swing Timer рекомендуется для обновления компонентов GUI, поскольку вызовы компонентов автоматически выполняются в потоке диспетчеризации событий (правильный поток для обновления компонентов на основе Swing или AWT).

Swing Timer имеет тенденцию «дрейфовать» во времени. Если вы создадите таймер, который срабатывает каждую секунду, через час или около того, он может дрейфовать на несколько секунд выше или ниже истекшего времени.

При использовании Swing Timer для обновления дисплея, который должен быть точным (например, таймер обратного отсчета / секундомер), как мы можем избежать этого временного смещения?

1 Ответ

3 голосов
/ 06 января 2020

Хитрость здесь в том, чтобы отслеживать истекшее время, часто проверять его и обновлять GUI, когда фактическое требуемое время (в данном случае «одна секунда») прошло.

Вот пример этого. Обратите внимание на комментарии в коде.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;

public class NonDriftingCountdownTimer {

    private JComponent ui = null;
    private Timer timer;
    private JLabel outputLabel;

    NonDriftingCountdownTimer() {
        initUI();
    }

    /** Keeps track of the start time and adjusts the count 
     * based on the ELAPSED time.
     * This should be used with a short time between listener calls. */
    class TimerActionListener implements ActionListener {
        long start = -1l;
        int duration;
        int count = 0;

        TimerActionListener(int duration) {
            this.duration = duration;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            long time = System.currentTimeMillis();
            if (start<0l) {
                start = time;
            } else {
                long next = start+(count*1000);
                if (time>next) {
                    count++;
                    outputLabel.setText((duration-count)+"");
                    if (count==duration) {
                        timer.stop();
                        JOptionPane.showMessageDialog(
                                outputLabel, "Time Is Up!");
                    }
                }
            }
        }
    }

    public final void initUI() {
        if (ui!=null) return;

        ui = new JPanel(new BorderLayout(4,4));
        ui.setBorder(new EmptyBorder(4,4,4,4));

        JPanel controlPanel = new JPanel();
        ui.add(controlPanel, BorderLayout.PAGE_START);
        final SpinnerNumberModel durationModel = 
                new SpinnerNumberModel(10, 1, 1200, 1);
        JSpinner spinner = new JSpinner(durationModel);
        controlPanel.add(spinner);

        JButton startButton = new JButton("Start");
        ActionListener startListener = (ActionEvent e) -> {
            int duration = durationModel.getNumber().intValue();
            TimerActionListener timerActionListener =
                    new TimerActionListener(duration);
            if (timer!=null) { timer.stop(); }
            // Note the short time of fire. This will allow accuracy
            // to within 1/50th of a second (without gradual drift).
            timer = new Timer(20, timerActionListener);
            timer.start();
        };
        startButton.addActionListener(startListener);
        controlPanel.add(startButton);

        outputLabel = new JLabel("0000", SwingConstants.TRAILING);
        outputLabel.setFont(new Font(Font.MONOSPACED, Font.BOLD, 200));
        ui.add(outputLabel);
    }

    public JComponent getUI() {
        return ui;
    }

    public static void main(String[] args) {
        Runnable r = () -> {
            try {
                UIManager.setLookAndFeel(
                        UIManager.getSystemLookAndFeelClassName());
            } catch (Exception useDefault) {
            }
            NonDriftingCountdownTimer o = new NonDriftingCountdownTimer();

            JFrame f = new JFrame(o.getClass().getSimpleName());
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setLocationByPlatform(true);

            f.setContentPane(o.getUI());
            f.pack();
            f.setMinimumSize(f.getSize());

            f.setVisible(true);
        };
        SwingUtilities.invokeLater(r);
    }
}
...