Swing 是一个单线程环境。也就是说,所有更新和交互都在单个线程中执行。 Swing 也是非线程安全的。这意味着 UI 的所有更新必须在该线程(事件调度线程或 ETD)的上下文中执行。
任何阻止 EDT 的代码都会阻止它(除其他外)重新绘制 UI 并响应用户的输入。
您的绘制代码永远不会更新屏幕,实际上它会使您的应用程序看起来“挂起”,因为不允许完成 paint 方法并阻止 ETD。
paint方法被调用后会快速返回并可能快速连续重复调用,这是一个例外。
一般来说,Thread 可能有点过头了,在这种情况下,javax.swing.Timer 之类的东西会更合适。
public class AnimatedBoat {
public static void main(String[] args) {
new AnimatedBoat();
}
public AnimatedBoat() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new AnimationPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class AnimationPane extends JPanel {
private BufferedImage boat;
private int xPos = 0;
private int direction = 1;
public AnimationPane() {
try {
boat = ImageIO.read(new File("boat.png"));
Timer timer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
xPos += direction;
if (xPos + boat.getWidth() > getWidth()) {
xPos = getWidth() - boat.getWidth();
direction *= -1;
} else if (xPos < 0) {
xPos = 0;
direction *= -1;
}
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
} catch (IOException ex) {
ex.printStackTrace();
}
}
@Override
public Dimension getPreferredSize() {
return boat == null ? super.getPreferredSize() : new Dimension(boat.getWidth() * 4, boat.getHeight());
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int y = getHeight() - boat.getHeight();
g.drawImage(boat, xPos, y, this);
}
}
}
作为旁注。您应该很少需要覆盖顶级容器的paint 方法,例如JApplet 或JFrame,虽然这样做有很多充分的理由,但您最感兴趣的是事实它们不是双缓冲的,这意味着您可能会在屏幕更新时看到闪烁。
最好使用JPanel 之类的东西并覆盖它的paintComponent 方法。
看看
更多信息
nb
虽然我在示例中使用了JFrame,但将动画面板放入JApplet 是一件简单的事情,这是您不需要/不想从顶层扩展的另一个原因容器;)