java - 发行: Creating a very accurate Swing Timer

标签 java swing timer event-dispatch-thread

为了使SwingTimer准确,我喜欢@Tony Docherty建议的逻辑和示例
在CR。这是Link

为了突出显示给定的单词,一次又一次地总是有几微秒的延迟。如果我要突出显示一些单词:“hello how are”,并且每个单词的值分别(延迟)为:200,300,400 ms,则计时器实际花费的时间总是更多。说而不是200毫秒,而是216毫秒。像这样,如果我有很多话……最后,额外的延迟是显而易见的。

我必须突出显示每个字母说:'h'e'l'l'0'每个字母应获得200/length(即5)= 40 ms左右。设置每个字母后的延迟时间。

我的逻辑是,在开始该过程之前,以当前时间说startTime。另外,计算totalDelay,它是totalDelay + = delay/.length()。

现在检查条件:(startTime+totalDelay-System.currentTime)
如果这是-ve,则意味着时间消耗更多,因此请跳过字母。检查直到出现正延迟,这意味着我要添加到现在为止的计时,并以进程开始时所花费的时间差异来过度检查它。

这可能导致跳过以突出显示字母。

但是出了点问题。什么,我很难辨认。可能是循环的问题。我已经看到它只是两次进入循环(以检查时间是否为-ve)。但这不是事实。我也不确定是否要设置下一个延迟时间。有任何想法吗?

这是一个SSCCE:

    import java.awt.Color;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.lang.reflect.InvocationTargetException;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JTextPane;
    import javax.swing.SwingUtilities;
    import javax.swing.Timer;
    import javax.swing.text.BadLocationException;
    import javax.swing.text.DefaultStyledDocument;
    import javax.swing.text.StyleConstants;
    import javax.swing.text.StyledDocument;

    public class Reminder {
        private static final String TEXT = "arey chod chaad ke apnee saleem ki gali anarkali disco chalo";
        private static final String[] WORDS = TEXT.split(" ");
        private JFrame frame;
        private Timer timer;
        private StyledDocument doc;
        private JTextPane textpane;
        private int[] times = new int[100];
      private long totalDelay=0,startTime=0;

        private int stringIndex = 0;
        private int index = 0;

        public void startColoring() {
              times[0]=100;times[9]=200;times[10]=200;times[11]=200;times[12]=200;
              times[1]=400;times[2]=300;times[3]=900;times[4]=1000;times[5]=600;times[6]=200;times[7]=700;times[8]=700;

      ActionListener actionListener = new ActionListener() {
       @Override
       public void actionPerformed(ActionEvent actionEvent) 
       {

       doc.setCharacterAttributes(stringIndex, 1, textpane.getStyle("Red"), true);
        stringIndex++;

 try {

 if (stringIndex >= doc.getLength() || doc.getText(stringIndex, 1).equals(" ")|| doc.getText(stringIndex, 1).equals("\n"))
 {
                            index++;
  }
    if (index < WORDS.length) {

       double delay = times[index];
     totalDelay+=delay/WORDS[index].length();

  /*Check if there is no -ve delay, and you are running according to the time*/
  /*The problem is here I think. It's just entered this twice*/
   while(totalDelay+startTime-System.currentTimeMillis()<0)
      { 
      totalDelay+=delay/WORDS[index].length();
      stringIndex++;
     /*this may result into the end of current word, jump to next word.*/
    if (stringIndex >= doc.getLength() || doc.getText(stringIndex, 1).equals(" ") || doc.getText(stringIndex, 1).equals("\n"))
       {
   index += 1;
   totalDelay+=delay/WORDS[index].length();
       }
      }

     timer.setDelay((int)(totalDelay+startTime-System.currentTimeMillis()));

                        } 
else {
         timer.stop();
    System.err.println("Timer stopped");
       }
                    } catch (BadLocationException e) {
                        e.printStackTrace();
                    }
                }
            };

            startTime=System.currentTimeMillis();
            timer = new Timer(times[index], actionListener);
            timer.setInitialDelay(0);
            timer.start();
        }

        public void initUI() {
            frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            JPanel panel = new JPanel();
            doc = new DefaultStyledDocument();
            textpane = new JTextPane(doc);
            textpane.setText(TEXT);
            javax.swing.text.Style style = textpane.addStyle("Red", null);
            StyleConstants.setForeground(style, Color.RED);
            panel.add(textpane);
            frame.add(panel);
            frame.pack();
            frame.setVisible(true);
        }

        public static void main(String args[]) throws InterruptedException, InvocationTargetException {
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    Reminder reminder = new Reminder();
                    reminder.initUI();
                    reminder.startColoring();
                }
            });
        }
    }

更新:

为了更好的理解:

@Tony Docherty给出的EG:

让我们以单词“Test”为例,说它需要突出显示1秒钟,因此每个字母都突出显示250ms。
按照您原来的方式做事,确实意味着您为每个字母设置了250ms的计时器,但是如果每个周期实际上花费了260ms,并且可以说“e”周期花费了400ms(可能是由于GC或其他使用CPU周期的原因),结束时,您将需要多花180毫秒的时间。该错误将继续为每个单词生成,直到该错误太大为止,突出显示不再在视觉上同步。

我正在尝试的方法不是重复说该字母需要突出显示x的时间量,而是计算每个字母相对于序列开头的时间,即T = 250,e = 500,s = 750,t = 1000。

因此,要获取实际的时间延迟,您需要添加开始时间并减去当前时间。为了使用上面给出的时间来运行示例:
StartTime   Letter   Offset    CurrentTime    Delay  ActualTimeTaken   
100000         T       250       100010        240      250  
100000         e       500       100260        240      400  
100000         s       750       100660         90      100  
100000         t      1000       100760        240      250  

因此,您现在应该可以看到,每个字母的时间已进行调整,以考虑到前一个字母的任何时间超限。当然,时间超限有可能太大,以致您不得不跳过突出显示下一个字母(或者可能超过1个字母)的情况,但至少我会保持大致同步。

编辑的SSCCE

更新2

在第一阶段,我会为每个单词安排时间。也就是说,当用户按下ESC键时,将存储一个特定单词的时间(当在后台播放歌曲时,他会这样做。)按下ESC键时,当前单词会突出显示,并且当前单词所花费的时间单词存储在数组中。我继续存储时间。当用户结束时,现在我想按照设置的时间突出显示单词。因此,在这里,用户的时间安排很重要。如果时间安排得很快,那么单词的突出显示也要快,反之亦然。

新更新:进度

以下答案具有不同的逻辑,但令我惊讶的是,它们的工作原理大致相同。我在所有逻辑(包括我的逻辑)中发现的一个非常非常奇怪的问题是,它们似乎只适用于几行,但是在获得速度之后,这也不算慢,但有很大的不同。

另外,如果您认为我应该以不同的方式思考,那么您的建议也将受到高度赞赏。

最佳答案

好的,所以我一直在看一些代码(我在上一个关于卡拉OK计时器的问题中发布的代码)

使用该代码,我通过System.nanoTime()建立了一个使用System.out.println()的测量系统,这将有助于我们了解正在发生的情况:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

public class KaraokeTest {

    private int[] timingsArray = {1000, 1000, 9000, 1000, 1000, 1000, 1000, 1000, 1000, 1000};//word/letters timings
    private String[] individualWordsToHighlight = {" \nHello\n", " world\n", " Hello", " world", " Hello", " world", " Hello", " world", " Hello", " world"};//each individual word/letters to highlight
    private int count = 0;
    private final JTextPane jtp = new JTextPane();
    private final JButton startButton = new JButton("Start");
    private final JFrame frame = new JFrame();
    //create Arrays of individual letters and their timings
    final ArrayList<String> chars = new ArrayList<>();
    final ArrayList<Long> charsTiming = new ArrayList<>();

    public KaraokeTest() {
        initComponents();
    }

    private void initComponents() {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);

        for (String s : individualWordsToHighlight) {
            String tmp = jtp.getText();
            jtp.setText(tmp + s);
        }
        jtp.setEditable(false);

        startButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                startButton.setEnabled(false);
                count = 0;
                charsTiming.clear();
                chars.clear();

                for (String s : individualWordsToHighlight) {
                    for (int i = 0; i < s.length(); i++) {
                        chars.add(String.valueOf(s.charAt(i)));
                        //System.out.println(String.valueOf(s.charAt(i)));
                    }
                }

                //calculate each letters timings
                for (int x = 0; x < timingsArray.length; x++) {
                    for (int i = 0; i < individualWordsToHighlight[x].length(); i++) {
                        individualWordsToHighlight[x] = individualWordsToHighlight[x].replace("\n", " ").replace("\r", " ");//replace line breaks
                        charsTiming.add((long) (timingsArray[x] / individualWordsToHighlight[x].trim().length()));//dont count spaces
                        //System.out.println(timingsArray[x] / individualWordsToHighlight[x].length());
                    }
                }

                Timer t = new Timer(1, new AbstractAction() {
                    long startTime = 0;
                    long acum = 0;
                    long timeItTookTotal = 0;
                    long dif = 0, timeItTook = 0, timeToTake = 0;
                    int delay = 0;

                    @Override
                    public void actionPerformed(ActionEvent ae) {
                        if (count < charsTiming.size()) {

                            if (count == 0) {
                                startTime = System.nanoTime();
                                System.out.println("Started: " + startTime);
                            }

                            timeToTake = charsTiming.get(count);
                            acum += timeToTake;

                            //highlight the next word
                            highlightNextWord();

                            //System.out.println("Acum " + acum);
                            timeItTook = (acum - ((System.nanoTime() - startTime) / 1000000));
                            timeItTookTotal += timeItTook;
                            //System.out.println("Elapsed since start: " + (System.nanoTime() - startTime));
                            System.out.println("Time the char should take: " + timeToTake);
                            System.out.println("Time it took: " + timeItTook);
                            dif = (timeToTake - timeItTook);
                            System.out.println("Difference: " + dif);
                            //System.out.println("Difference2 " + (timeToTake - dif));

                            //calculate start of next letter to highlight less the difference it took between time it took and time it should actually take
                            delay = (int) (timeToTake - dif);

                            if (delay < 1) {
                                delay = 1;
                            }

                            //restart timer with new timings
                            ((Timer) ae.getSource()).setInitialDelay((int) timeToTake);//timer is usually faster thus the entire highlighting will be done too fast
                            //((Timer) ae.getSource()).setInitialDelay(delay);
                            ((Timer) ae.getSource()).restart();

                        } else {//we are at the end of the array
                            long timeStopped = System.nanoTime();
                            System.out.println("Stopped: " + timeStopped);
                            System.out.println("Time it should take in total: " + acum);
                            System.out.println("Time it took using accumulator of time taken for each letter: " + timeItTookTotal
                                    + "\nDifference: " + (acum - timeItTookTotal));
                            long timeItTookUsingNanoTime = ((timeStopped - startTime) / 1000000);
                            System.out.println("Time it took using difference (endTime-startTime): " + timeItTookUsingNanoTime
                                    + "\nDifference: " + (acum - timeItTookUsingNanoTime));
                            reset();
                            ((Timer) ae.getSource()).stop();//stop the timer
                        }
                        count++;//increment counter
                    }
                });
                t.setRepeats(false);
                t.start();
            }
        });

        frame.add(jtp, BorderLayout.CENTER);
        frame.add(startButton, BorderLayout.SOUTH);

        frame.pack();
        frame.setVisible(true);
    }

    private void reset() {
        startButton.setEnabled(true);
        jtp.setText("");
        for (String s : individualWordsToHighlight) {
            String tmp = jtp.getText();
            jtp.setText(tmp + s);
        }
        JOptionPane.showMessageDialog(frame, "Done");
    }

    private void highlightNextWord() {
        //we still have words to highlight
        int sp = 0;
        for (int i = 0; i < count + 1; i++) {//get count for number of letters in words (we add 1 because counter is only incrementd after this method is called)
            sp += 1;
        }

        while (chars.get(sp - 1).equals(" ")) {
            sp += 1;
            count++;
        }

        //highlight words
        Style style = jtp.addStyle("RED", null);
        StyleConstants.setForeground(style, Color.RED);
        ((StyledDocument) jtp.getDocument()).setCharacterAttributes(0, sp, style, true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new KaraokeTest();
            }
        });
    }
}

我的电脑上的输出是:

Started: 10289712615974

Time the char should take: 166

Time it took: 165

Difference 1

...

Time the char should take: 166

Time it took: 155

Difference 11

...

Time the char should take: 166

Time it took: 5

Difference 161

Stopped: 10299835063084

Time it should take in total: 9960

Time it took using accumulator of time taken for each letter: 5542

Difference: 4418

Time it took using difference (endTime-startTime): 10122

Difference: -162



因此,我的结论是Swing计时器的运行速度实际上比我们预期的要快,因为Timer s actionPerformed中的代码不一定会花费与预期的字母突出显示时间一样长的时间,这当然会引起雪崩效应,即计时器运行的更快/更慢。差异将变得更大/更少,并且在restart(..)上执行下一个计时器的时间将在不同的时间,即更快或更慢。

在代码中执行以下操作:
//calculate start of next letter to highlight less the difference it took between time it took and time it should actually take
delay = (int) (timeToTake - dif);


//restart timer with new timings
//((Timer) ae.getSource()).setInitialDelay((int)timeToTake);//timer is usually faster thus the entire highlighting will be done too fast
((Timer) ae.getSource()).setInitialDelay(delay);
((Timer) ae.getSource()).restart();

产生更准确的结果(Ive的最大延迟是每个字母快4毫秒):

Started: 10813491256556

Time the char should take: 166

Time it took: 164

Difference 2

...

Time the char should take: 166

Time it took: 164

Difference 2

...

Time the char should take: 166

Time it took: 162

Difference 4

Stopped: 10823452105363

Time it should take in total: 9960

Time it took using accumulator of time taken for each letter: 9806

Difference: 154

Time it took using difference (endTime-startTime): 9960

Difference: 0

关于java - 发行: Creating a very accurate Swing Timer,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15040989/

相关文章:

c++ - 如何安全地删除 C++ 中的 posix 计时器?

java - 如何从另一个线程暂停和恢复 Java 中的线程

linux - 定时器中断和进程滴答

java - 具有定义的匿名类实例化 - 覆盖 protected 方法

java - 如何仅使用正则表达式验证 YouTube 嵌入链接

java - 为什么我的 Swing GUI 无法从一种 GUI 切换到另一种 GUI?

java - Int 中的构造函数 String

javascript - 防止隐藏选项卡上的 Chrome 计时器以 1 秒分辨率运行

java - Apache Shiro 中的实例级访问控制

java - 我在 AlertDialog 中遇到 CustomGridView 错误