【问题标题】:Simple Java Animation using JFrame and JPanel使用 JFrame 和 JPanel 的简单 Java 动画
【发布时间】:2014-12-14 22:48:33
【问题描述】:

好的,所以程序的目的只是绘制和椭圆形,然后在屏幕上移动它。代码在 Eclipse 上编译没有错误,但是在运行时,没有绘制或在屏幕上移动椭圆。我一直在研究,似乎线程必须做很多事情,但是这个简单的程序需要一个吗?我显然是使用 Swing 进行 GUI 编程的新手,因此我将不胜感激解释或链接到一个有关线程或此类相关概念的程序的任何添加。

public class Game extends JPanel
{
    int x =0;
    int y =0;

    private void moveBall()
    {

        x+=1;
        y+=1;
    }

    public void paint (Graphics g)
    {
        super.paint(g);
        g.fillOval(x, y, 30, 30);
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Animation");
        Game game = new Game();
        frame.add(game);
        frame.setVisible(true);
        frame.setSize(300,400);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        while (true)
        {
            game.moveBall();
            game.repaint();

        }
    }
}

【问题讨论】:

  • 实际上,可能是,问题很可能是,它移动得太快了,以至于它从未更新到屏幕上......
  • 可能是问题所在:您尝试在 while 循环中移动球。要么这会移动得太快,要么它甚至不会出现在框架中,因为框架本身没有被重新绘制。可能不是因为您甚至没有看到椭圆形而没有重新粉刷框架。使用计时器
  • 好的,那我该如何降低速度呢?使用线程可以/更容易做到这一点吗?那是 thread.sleep(millisecond param) 的作用吗? @MadProgrammer
  • 如何做到这一点? @Quincunx
  • 对于JPanelpublic void paint (Graphics g) { super.paint(g); 代码应为public void paintComponent(Graphics g) { super.paintComponent(g);..

标签: java multithreading swing animation


【解决方案1】:

可能的问题是,线程对于 UI 来说运行得太快了,在“球”离开可见区域后 UI 显示得很好。

你需要做几件事......

首先,您需要确保在事件调度线程中正确安排更新,其次,更新之间有短暂的延迟。例如,25fps 大约是更新之间的 40 毫秒延迟,60fps 大约是 16 毫秒

有多种方法可以实现这一点,具体取决于您希望实现的目标,例如,您可以简单地使用Thread.sleep 使线程在更新之间暂停一小段时间。这样做的问题是 Swing 不是线程安全的,对 UI 的所有更新都应该在 Event Dispatching Thread 的上下文中进行。

虽然您的程序很简单,但在您更新它的状态时,绘制周期可能会运行,从而导致脏更新。

另一种解决方案可能是使用 Swing Timer,它允许您定期安排更新,这些更新在事件调度线程的上下文中触发,使其使用起来更安全。

查看Concurrency in SwingHow to use Swing Timers 了解更多详情。

举个例子……

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class BallAnimation {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        new BallAnimation();
    }

    public BallAnimation() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private int x = 0;
        private int y = 0;

        public TestPane() {
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    moveBall();
                    repaint();
                }
            });
            timer.start();
        }

        protected void moveBall() {
            x++;
            y++;
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.RED);
            g2d.fillOval(x, y, 30, 30);
            g2d.dispose();
        }

    }

}

附带说明,除非您确实有理由这样做,否则应避免覆盖paint,而应使用paintComponent

【讨论】:

  • 好的,这很有帮助。感谢您推荐线程不安全。我添加的只是用内部计时器的语句构建的游戏,它可以工作。但是,我对 BallAnimation 构造函数的行中的内容有些迷茫(框架语句除外)。您能否总结一下这到底是什么以及为什么有必要?提前致谢。 @MadProgammer
  • 基本上,EventQueue.invokeLater 确保在事件调度线程的上下文中创建和显示 UI(使其线程安全),有关更多详细信息,请参阅 Initial ThreadsUIManager.setLookAndFeel 只是将 Swing 使用的观感设置为当前系统的观感,我不喜欢金属感
  • 那么如果我使用 EventQueue.invokeLater 使程序线程安全,那么我是否能够安全地使用 Adem 的线程方法? @MadProgrammer
  • 没有。 “主”线程(调用main 方法)和事件调度线程是两个不同的线程。从“主”线程(或任何其他线程)更新 UI 将违反 Swing 的单线程规则。 “简单”的解决方案是使用 Swing Timer,因为它在 EDT 的上下文中同步 UI 所需的更新(您不能同时更新状态和绘制)。您可以手动执行此操作,但这会增加解决方案的复杂性。
  • 如果您使用BufferStrategy,您可以获得对绘制过程的控制权,那么这并不重要,但您会增加解决方案的复杂性...
【解决方案2】:

尝试使用 sleep 循环作为适合代码的最简单方法。 main 实际上是一个线程。并且JFrame 创建自己的线程。

while (true)
{
   game.moveBall();
   game.repaint();
   try { Thread.sleep(50); } catch (Exception e){}
}

我刚刚意识到,您不会用默认颜色绘制整个屏幕。

把你的绘画方法改成这个

public void paint (Graphics g)
{
    super.paint(g);
    g.setColor(Color.white); //default color
    g.fillRect(0, 0, getWidth(), getHeight()); // fill whole canvas
    g.setColor(Color.black); //change color
    g.fillOval(x, y, 30, 30); // draw oval
}

【讨论】:

  • 仅供参考:Swing 本身不是线程安全的,虽然只是一个简单的例子,更复杂的更新过程可能会导致脏更新,因为绘制周期可能独立于程序员的请求而发生
  • 是的,但他只是想画一个简单的椭圆形。作为我的解释,这是最简单的方法
  • “我刚刚意识到,你不会用不同的颜色绘制整个屏幕。” - paint,通过调用paintComponent(通过调用super.paint ) 会这样做。请参阅Painting in AWT and SwingPerforming Custom Painting 了解更多详情
  • 简单而误导
  • 如果这是你的方法,那么你做错了。如果你要教别人射击,你不会只是教他们如何扣动扳机。你会先教他们枪支安全。这里也一样。我们需要教人们如何在 API / 框架的要求范围内工作,这将有助于防止他们在增加问题的复杂性时“误伤自己”。从坚固的基础上建造,你的房子不会倒塌;)
猜你喜欢
  • 2019-02-22
  • 2011-05-25
  • 1970-01-01
  • 2013-10-11
  • 2022-06-19
  • 1970-01-01
  • 2011-10-04
  • 2011-04-20
  • 2012-10-24
相关资源
最近更新 更多