2013-01-16 39 views
4

我有两个几乎相同的类:AnimationFrame1和AnimationFrame2。这两个类都显示一个蓝色球在500 x 500窗口中水平来回移动。除了runAnimation()和createAndShowGUI()方法外,这两个类是相同的。在其runAnimation()方法中,AnimationFrame1使用while循环和sleep方法来创建动画循环,而AnimationFrame2使用Swing Timer。在它的createAndShowGUI()方法中,AnimationFrame1创建一个新线程并在其上调用runAnimation()方法,而AnimationFrame2只是简单地调用没有新线程的runAnimation()方法。为什么Java swing定时器比睡眠导致动画不稳定?

在编译这两个类之后,我发现使用Swing Timer的AnimationFrame2会显示更平滑的动画,与AnimationFrame1(使用while循环和sleep方法)中显示的动画不一样多。我的问题是:为什么AnimationFrame1的动画显示比AnimationFrame2更多?我为此寻找了一个理由,但迄今为止一无所获。另外,我显然是一个Java新手,所以请让我知道,如果你看到我的代码有任何问题,或者如果你知道我可以改进它的任何方式。

这里是AnimationFrame1:

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.ActionListener; 
import java.awt.event.ActionEvent; 
import java.awt.image.BufferedImage; 

class AnimationFrame1 extends JPanel { 

    int ovalX; 
    int prevX; 
    Timer timer; 
    boolean moveRight; 
    BufferedImage img; 

    public AnimationFrame1() { 
     setPreferredSize(new Dimension(500, 500)); 
    } 

    public void runAnimation() { 
     moveRight = true; 
     img = null; 
     ovalX = 0; 
     prevX = 0; 
     while(true) { 
      if (moveRight == true) { 
       prevX = ovalX; 
       ovalX = ovalX + 4; 
      } 
      else { 
       prevX = ovalX - 4; 
       ovalX = ovalX - 4; 
      } 
      repaint(); 
      if (ovalX > 430) { 
       moveRight = false; 
      } 
      if (ovalX == 0) { 
       moveRight = true; 
      } 
      try { 
       Thread.sleep(25); 
      } 
      catch(Exception e) { 
      } 
     } 
    } 

    public void paintComponent(Graphics g) { 
     if (img == null) { 
      GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 
      GraphicsDevice gs = ge.getDefaultScreenDevice(); 
      GraphicsConfiguration gc = getGraphicsConfiguration(); 
      img = gc.createCompatibleImage(78, 70); 
      Graphics gImg = img.getGraphics(); 
      gImg.setColor(getBackground()); 
      gImg.fillRect(0, 0, getWidth(), getHeight()); 
      gImg.setColor(Color.BLUE); 
      gImg.fillOval(4, 0, 70, 70); 
      gImg.dispose(); 
     } 
     g.drawImage(img, ovalX, 250, null); 
    } 

    public static void createAndShowGUI() { 
     JFrame mainFrame = new JFrame(); 
     final AnimationFrame1 animFrame = new AnimationFrame1(); 
     mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     mainFrame.add(animFrame); 
     mainFrame.pack(); 
     mainFrame.createBufferStrategy(2); 
     mainFrame.setVisible(true); 
     new Thread(new Runnable() { 
      public void run() { 
       animFrame.runAnimation(); 
      } 
     }).start(); 
    }  

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

} 

这里是AnimationFrame2:

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.ActionListener; 
import java.awt.event.ActionEvent; 
import java.awt.image.BufferedImage; 

class AnimationFrame2 extends JPanel { 

    int ovalX; 
    int prevX; 
    Timer timer; 
    boolean moveRight; 
    BufferedImage img; 

    public AnimationFrame2() { 
     setPreferredSize(new Dimension(500, 500)); 
    } 

    public void runAnimation() { 
     moveRight = true; 
     img = null; 
     ovalX = 0; 
     prevX = 0; 
     timer = new Timer(25, new ActionListener() { 
      public void actionPerformed(ActionEvent ae) { 
       if (moveRight == true) { 
        prevX = ovalX; 
        ovalX = ovalX + 4; 
       } 
       else { 
        prevX = ovalX - 4; 
        ovalX = ovalX - 4; 
       } 
       repaint(); 
       if (ovalX > 430) { 
        moveRight = false; 
       } 
       if (ovalX == 0) { 
        moveRight = true; 
       } 
      } 
     }); 
     timer.start(); 
    } 

    public void paintComponent(Graphics g) { 
     if (img == null) { 
      GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 
      GraphicsDevice gs = ge.getDefaultScreenDevice(); 
      GraphicsConfiguration gc = getGraphicsConfiguration(); 
      img = gc.createCompatibleImage(78, 70); 
      Graphics gImg = img.getGraphics(); 
      gImg.setColor(getBackground()); 
      gImg.fillRect(0, 0, getWidth(), getHeight()); 
      gImg.setColor(Color.BLUE); 
      gImg.fillOval(4, 0, 70, 70); 
      gImg.dispose(); 
     } 
     g.drawImage(img, ovalX, 250, null); 
    } 

    public static void createAndShowGUI() { 
     JFrame mainFrame = new JFrame(); 
     final AnimationFrame2 animFrame = new AnimationFrame2(); 
     mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     mainFrame.add(animFrame); 
     mainFrame.pack(); 
     mainFrame.createBufferStrategy(2); 
     mainFrame.setVisible(true); 
     animFrame.runAnimation(); 
    }  

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

} 

回答

8

把标记代码后,似乎定时器版本实际上每隔30毫秒,而Thread.sleep代码版本每25毫秒运行一次。可能有几种解释,包括:

  • 计时器的分辨率,这是不作为的Thread.sleep
  • 的事实,计时器是单线程(除了等待的好,一切都在运行在美国东部时间),所以如果一个任务(如重绘)花的时间超过25毫秒,就会耽误下一个任务

如果我增加睡眠至30ms的2个动画是相似的(实际数量可能会因您的计算机上有所不同)。

注意:Thread.sleep版本中存在潜在的线程安全问题。您在工作线程和UI线程之间共享变量而没有正确同步。虽然repaint似乎在内部引入了一个同步屏障,确保工作线程对UI线程所做更改的可见性,但这是一种偶然效应,并且明确确保可见性将是一种更好的做法,例如通过声明变量易挥发。

+0

他如何睡在UI线程? 'Thread.sleep()'方法是从'runAnimation()'方法调用的,该方法在一个'Runnable'内被调用,传递给一个新的'Thread'。 – BenCole

+1

@BenCole谢谢指出 - 我已经修改。 – assylias

+0

@assylias感谢您的帮助,但使用volatile时没有任何变化。当你使用动画时,你会获得更流畅的动画吗? – CogStudent

2

问题的原因很可能是由于第一版中的“违反”AWT语义。你不能在以外运行gui更新代码。

更新:即使repaint()方法可以安全地从另一个线程调用,它所做的只是排队将在EDT上运行的事件。这意味着在修改椭圆形的线程和读取它的线程EDT线程之间存在竞争条件。这将导致移动不均匀,因为绘图代码可能会看到与信令代码不同的值。

+1

只调用repaint方法 - 这是少数可以安全地从非UI线程调用的方法:http://stackoverflow.com/questions/9786497/safe-to-use-component-repaint-outside-edt – assylias

+1

@assylias - 更新。 – jtahlborn