簡體   English   中英

為什么Java swing計時器導致動畫口吃少於睡眠?

[英]Why does Java swing timer lead to less animation stutter than sleep?

我有兩個幾乎相同的類:AnimationFrame1和AnimationFrame2。 這兩個類都顯示一個藍色的球在500 x 500的窗口上水平來回移動。 除了runAnimation()和createAndShowGUI()方法之外,這兩個類是相同的。 在其runAnimation()方法中,AnimationFrame1使用while循環和sleep方法創建動畫循環,而AnimationFrame2使用Swing Timer。 在其createAndShowGUI()方法中,AnimationFrame1創建一個新線程並在其上調用runAnimation()方法,而AnimationFrame2只調用沒有新線程的runAnimation()方法。

在編譯了這兩個類之后,我發現使用Swing Timer的AnimationFrame2顯示了一個更平滑的動畫,它不會像使用while循環和sleep方法的AnimationFrame1中顯示的動畫那樣斷斷續續。 我的問題是:為什么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();
            }
        });
    }

}

將標記放入代碼后,似乎Timer版本實際上每30毫秒運行一次,而Thread.sleep版本每25毫秒運行一次。 可能有幾種解釋,包括:

  • 定時器的分辨率,不如Thread.sleep的分辨率
  • 計時器是單線程的事實(除了等待,一切都在EDT中運行)所以如果任務(如重新繪制)需要超過25毫秒,它將延遲下一個任務

如果我將睡眠時間增加到30毫秒,則2個動畫類似(實際數量可能因機器而異)。

注意:Thread.sleep版本中存在潛在的線程安全問題。 您在工作線程和UI線程之間共享變量而沒有正確的同步。 雖然看起來repaint內部引入了同步障礙,該同步障礙確保了工作線程從UI線程所做的更改的可見性,但它是一種偶然的效果,並且明確確保可見性是更好的做法,例如通過聲明變量易揮發。

出現此問題的原因很可能是由於第一版中“違反”AWT語義。 不能在EDT之外運行gui更新代碼。

更新:即使repaint()方法可以安全地從另一個線程調用,它所做的只是排隊將在EDT上運行的事件。 這意味着在修改ovalx的線程和正在讀取它的線程EDT線程之間存在競爭條件。 這將導致移動不均勻,因為繪圖代碼可能看到與信令代碼意圖不同的值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM