Требуется помощь по остановке таймера, когда произошло изменение ползунка

Я пытаюсь создать анимацию, импортируя изображения из листа спрайтов и изменяя скорость с помощью таймера. Когда я устанавливаю скорость в первый раз, она работает отлично, но в любое время после этого она не изменит скорость. Воспроизведение продолжится с предыдущей скоростью, и я получаю эту ошибку на выходе: https://imgur.com/a/sWhmQ

Будем признательны за любую помощь.
Вот что у меня есть:

Редактировать: Нашел проблему для timerTask и переместил ее в ChangeListener, однако скорость по-прежнему не меняется при перемещении ползунка.

import javax.swing.*;
import javax.swing.event.*;
import java.awt.event.*;
import java.util.Timer;
import java.util.TimerTask;

public class AnimationGUI {

    private static int counter = 0;
    private static JLabel value = new JLabel("0");
    private static JLabel image = new JLabel("");
    private static Timer timer = new Timer();

    public static void main(String[] args) {
        JFrame frame = new JFrame("Animation GUI");
        JPanel panel = new JPanel();
        JSlider slider = new JSlider(JSlider.HORIZONTAL, 1, 10, 1);

        slider.addChangeListener(new Slider());

        frame.setVisible(true);
        frame.setSize(500, 500);
        frame.setResizable(false);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(panel);

        panel.add(slider);
        panel.add(value);
        panel.add(image);
    }

    private static class Slider implements ChangeListener {

        public void stateChanged(ChangeEvent event) {
            JSlider source = (JSlider) event.getSource();

            TimerTask task = new TimerTask() {
                public void run() {
                    image.setIcon(new ImageIcon(counter + ".png"));
                    counter++;
                    if (counter > 12) {
                        counter = 0;
                    }
                }
            };

            if (!source.getValueIsAdjusting()) {
                value.setText("" + (int) source.getValue());
                int speed = source.getValue() * 100;
                timer.scheduleAtFixedRate(task, 0, speed);
            }
        }
    }
}

person Aglowe    schedule 23.02.2017    source источник
comment
Первая рекомендация: используйте Swing Timer вместо TimerTask, помимо того, что он автономен и поддерживает такие вещи, как stop, start и restart, его также безопасно использовать для обновления пользовательского интерфейса в Swing - Swing не является потокобезопасным   -  person MadProgrammer    schedule 23.02.2017
comment
Побейте меня до предела — используйте javax.swing.Timer, а не java.util.Timer.   -  person Hovercraft Full Of Eels    schedule 23.02.2017
comment
Я не согласен с MadProgrammer. Таймеры Swing действительно могут испортить EDT, и их не это сложно закодировать таким образом, чтобы избежать появления ошибок параллелизма. Производительность сильно пострадает при использовании Swing Timers. Но это одна из тех ситуаций, когда соглашаются или не соглашаются. В настоящее время я предпочитаю использовать JavaFX AnimationTimer.   -  person Phil Freihofner    schedule 23.02.2017
comment
@PhilFreihofner Так что SwingUtiltities.invokeLater, synchronized также засорили бы EDT, но вы правы, это не ВСЕГДА лучшее решение, но в этом случае я бы сказал, что это самое простое - в какой-то момент вам придется получить данные из того, какой другой поток использовался для EDT, таким образом, чтобы не нарушать однопоточный характер API - мне бы очень хотелось посмотреть, как вы примените JavaFX AnimationTimer к Swing, в качестве сравнения   -  person MadProgrammer    schedule 23.02.2017
comment
Не публикуйте изображения текста! Скопируйте/вставьте сам текст как изменить вопрос.   -  person Andrew Thompson    schedule 23.02.2017
comment
Нет пути назад! На данный момент я действительно предпочитаю JavaFX. Запланировать одноразовую задачу на EDT не так уж сложно, но для цикла анимации это дорого. Я думаю, что есть книга Killer Game Programming, где автор сравнивает игровой цикл вычисления (с использованием Thread.sleep), таймер Util и таймер Swing. Util и sleep-based (его преф) работали одинаково хорошо, Swing Timer был намного хуже по производительности. Java Concurrency In Practice (Goetz) рекомендует ExecutorService, FixedThreadPool как более надежный, чем Util.Timer, но многие пугаются.   -  person Phil Freihofner    schedule 23.02.2017
comment
@PhilFreihofner Да, но Swing TimerSwingWorker) используют SwingUtilities.invokeLater для планирования обновлений, поэтому, если вы не нарушаете однопоточный характер API, как еще вы безопасно обновляете свойства пользовательского интерфейса? JavaFX работает так же (насколько я понимаю) - возможно, просто более эффективно, учитывая, что он будет генерировать исключения, если вы попытаетесь обновить пользовательский интерфейс вне контекста, это поток событий.   -  person MadProgrammer    schedule 23.02.2017
comment
Это большая тема, и мы находимся на грани того, чтобы спровоцировать StackOverflow, не могли бы вы получить предупреждение о разговоре. Согласен-не согласен, имхо. Книга Гетца (трудно читать), или Параллелизм программирования на JVM (Субраманиам - легче читать), или Практика функционального программирования в целом касается проблем, связанных с параллелизмом.   -  person Phil Freihofner    schedule 23.02.2017


Ответы (2)


Первая рекомендация: используйте Swing Timer вместо TimerTask, помимо того, что он автономен и поддерживает такие вещи, как stop, start и restart, его также безопасно использовать для обновления пользовательского интерфейса из-за того, что Swing не является потокобезопасным.

Что-то типа...

private static class Slider implements ChangeListener {

    private Timer timer;

    public Slider() {
        timer = new Timer(16, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                image.setIcon(new ImageIcon(counter + ".png"));
                counter++;
                if (counter > 12) {
                    counter = 0;
                }                    
            }
        });
        timer.start()
    }

    public void stateChanged(ChangeEvent event) {
        JSlider source = (JSlider) event.getSource();
        if (!source.getValueIsAdjusting()) {
            value.setText("" + (int) source.getValue());
            int speed = source.getValue() * 100;
            timer.setDelay(speed);
        }
    }
}

В качестве примера

Дополнительные сведения см. в разделе как использовать таймеры.

person MadProgrammer    schedule 23.02.2017

Подозреваю, что нужно отменить существующий TimerTask и запустить новый с нужной скоростью.

person Phil Freihofner    schedule 23.02.2017