4

AnimationFrame1とAnimationFrame2の2つのほぼ同じクラスがあります。これらのクラスは両方とも、500x500ウィンドウを横切って水平方向に前後に移動する青いボールを表示します。runAnimation()メソッドとcreateAndShowGUI()メソッドを除いて、2つのクラスは同じです。runAnimation()メソッドでは、AnimationFrame1はwhileループとsleepメソッドを使用してアニメーションループを作成しますが、AnimationFrame2はSwingタイマーを使用します。その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();
            }
        });
    }

}
4

2 に答える 2

8

コードにマーカーを配置した後、Timerバージョンは実際には30ミリ秒ごとに実行されるのに対し、Thread.sleepバージョンは25ミリ秒ごとに実行されるようです。次のようないくつかの説明があります。

  • タイマーの解像度。これはThread.sleepの解像度ほど良くありません。
  • タイマーがシングルスレッドであるという事実(待機を除いて、すべてがEDTで実行されます)。したがって、タスク(再描画など)に25ミリ秒以上かかる場合、次のタスクが遅延します。

スリープを30msに増やすと、2つのアニメーションは似ています(実際の数はマシンによって異なる場合があります)。

注:Thread.sleepバージョンには潜在的なスレッドセーフの問題があります。適切な同期を行わずに、ワーカースレッドとUIスレッドの間で変数を共有します。repaintUIスレッドからワーカースレッドによって行われた変更の可視性を保証する同期バリアを内部的に導入しているように見えますが、これは偶発的な影響であり、たとえば変数を揮発性として宣言することにより、可視性を明示的に確保することをお勧めします。 。

于 2013-01-16T23:44:08.500 に答える
2

この問題の理由は、最初のバージョンでのAWTセマンティクスの「違反」が原因である可能性が最も高いです。EDTの外部でGUI更新コードを実行 することはできません。

更新:repaint()メソッドが別のスレッドから安全に呼び出せる場合でも、EDTで実行されるイベントをキューに入れるだけです。これは、ovalxを変更するスレッドとそれを読み取るスレッドEDTスレッドの間に競合状態があることを意味します。これにより、描画コードがシグナリングコードの意図とは異なる値を認識する可能性があるため、動きが不均一になります。

于 2013-01-17T01:24:36.517 に答える