2

我正在尝试制作一个运行动画的应用程序。为此,我有一个 Jframe,其中包含我的 Jpanel 子类,动画在其中运行。这是我的两门课:

首先,这是我的驱动程序类:

import javax.swing.*;

public class Life {
    public static void main(String[] args){
         JFrame game = new JFrame("Life");

         game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         game.setSize(500, 500);

         MainPanel mainPanel = new MainPanel();
         game.setContentPane(mainPanel);


         game.setVisible(true);

     }
 }

其次,这是我的 Jpanel 子类:

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

public class MainPanel extends JPanel implements ActionListener{
    int i = 0;
    int j = 0;
    public MainPanel(){
        super();
    }

    public void paintComponent(Graphics g){
        j++;
        g.drawLine(10,10, 20 + i, 20 + i);
        Timer t = new Timer(1000, this);
        t.start();
    }

    @Override
    public void actionPerformed(ActionEvent actionEvent) {
        i++;
        repaint();
    }
}

请注意,每次调用 actionPreformed 时变量 i 都会递增,而每次调用 paintComponent 时都会调用变量 j。最终发生的事情是 i 开始比 j 大得多,而由paintComponent 绘制的线似乎以越来越快的速度增长。

以下是我的问题:

  • 为什么会这样?
  • 我怎样才能同步事情,以便每 1000 毫秒重绘一次?
  • 鉴于我正在尝试做的事情,我的方法是错误的吗?我应该以不同的方式做事吗?

提前致谢。

4

3 回答 3

4

不要从paintComponent方法中启动 Swing Timer。

这种方法应该做你的画,只有你的画,只有画。它应该绝对包含程序逻辑。了解您对此方法的控制非常有限,因为您无法预测何时或是否会调用它,以及调用它的频率。你甚至不能自己调用​​它,或者保证当你建议它被调用时repaint(),它实际上会被调用。

此外,此方法必须快速,尽可能快,因为任何减慢它的速度,无论是对象创建还是读取文件都会降低您的 GUI 的感知响应能力,这是您最不想看到的事情。

解决方案是将程序逻辑从该方法中分离出来,并分成更好的方法,例如您的构造函数。重复代码应该在 Swing Timer 中。

编辑:
你说:

我这样做只是为了测试一下。还有一个问题:如果paintComponent 或paintComponent 中的工作所依赖的某个线程花费超过1000 毫秒(或其他任何时间)来完成它的工作,会发生什么情况?我唯一能想到的就是让paintComponent 绘制到目前为止的动画进度,而不是等待动画到达下一步(如果这有意义的话)。想法?

永远不应该在 paintComponent 中使用需要那么长甚至 10 毫秒的代码。如果有发生类似情况的风险,则在后台线程中进行绘图并绘制到 BufferedImage,然后在 Swing 事件线程上使用该Graphics#drawImage(...)方法在 paintComponent 方法中显示 BufferedImage。

于 2012-09-30T05:27:09.907 回答
3

@HFoE 的基本见解的一些小补充:

  • 公共start()方法是确保在开始之前完全构建视图的便捷方法。
  • 字段具有明确定义的默认值,它们应该是private.
  • Swing GUI 对象应该事件分派线程上构建和操作。
  • 覆盖getPreferredSize()pack()封闭Window.

修改后的代码:

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class Life {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            private final JTabbedPane jtp = new JTabbedPane();

            @Override
            public void run() {
                JFrame game = new JFrame("Life");
                game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                MainPanel mainPanel = new MainPanel();
                game.setContentPane(mainPanel);
                game.pack();
                game.setVisible(true);
                mainPanel.start();
            }
        });
    }

    private static class MainPanel extends JPanel implements ActionListener {

        private Timer t = new Timer(100, this);
        private int i;
        private int j;

        @Override
        public void paintComponent(Graphics g) {
            g.drawLine(10, 10, 20 + i, 20 + i);
        }

        public void start() {
            t.start();
        }

        @Override
        public void actionPerformed(ActionEvent actionEvent) {
            i++;
            repaint();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(500, 500);
        }
    }
}
于 2012-09-30T05:42:08.977 回答
1

ATimer默认情况下继续运行。只有当你调用setRepeats( false )它才会停止。

所以以下几行

Timer t = new Timer(1000, this);
t.start();

在您的paintComponent方法中意味着经过几次重绘后,您将Timer运行许多实例,这解释了为什么i增加得那么快j

解决方案当然是将您移到方法Timer之外paintComponent,并坚持一个Timer实例。

进一步的评论(其他人没有说,不会重复他们非常有用的建议):

  • paintComponent永远不要在不调用方法的情况下重写super方法
  • 你不应该暴露ActionListener接口。只需在ActionListener内部使用
于 2012-09-30T07:52:54.007 回答