
本文详解如何在 swing 中实现平滑球体动画,重点解决因未正确调用 `super.paintcomponent(g)` 导致的图像残留、白屏和失真问题,并给出符合 swing 线程安全与绘制规范的标准实现。
在 Java Swing 中实现动画时,绝不能重写 JFrame.paint() 方法——这是常见误区。Swing 的绘制机制基于双缓冲与组件层级委托,直接覆盖顶级容器的 paint 会破坏默认的绘制链(如边框、标题栏渲染),并干扰 AWT/Swing 的脏区域管理逻辑,极易引发白屏、残影或 UI 锁死。
正确的做法是:将自定义绘制逻辑封装在继承自 JPanel 的组件中,并严格重写 paintComponent(Graphics g) 方法。关键有三步:
- 必须以 super.paintComponent(g) 开头:它负责清空上一帧的绘图内容(自动填充背景色)、触发双缓冲准备,并确保 Swing 绘制管线正常工作;
- 避免在 paintComponent 中执行耗时操作或修改状态:该方法可能被频繁、不可预测地调用(如窗口遮挡后恢复),状态变更应仅发生在事件监听器或独立更新逻辑中;
- 动画控制交由 javax.swing.Timer 驱动:它保证回调在 Event Dispatch Thread (EDT) 中执行,避免多线程并发修改 GUI 组件引发的 IllegalStateException。
以下是修复后的完整示例(已精简冗余字段,增强可读性):
import java.awt.*;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Animation extends JPanel {
private int x1 = 110, y1 = 350; // cyan ball
private int x2 = 55, y2 = 350; // green ball
private int x3 = 0, y3 = 350; // red ball
private final Timer timer;
public Animation() {
setPreferredSize(new Dimension(500, 500));
setBackground(Color.WHITE); // 显式设置背景色,避免默认透明导致残影
timer = new Timer(100, new MoveListener());
timer.start();
// 构建并显示主窗口
JFrame frame = new JFrame("MausKampf");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setContentPane(this);
frame.setResizable(false);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // ✅ 强制清除上一帧,启用双缓冲
Graphics2D g2d = (Graphics2D) g.create(); // 创建副本,避免影响后续绘制
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 绘制轨道线
g2d.setColor(Color.BLACK);
g2d.drawLine(0, 400, 500, 400);
g2d.drawLine(0, 350, 225, 350);
g2d.drawLine(275, 350, 500, 350);
g2d.drawLine(225, 350, 225, 0);
g2d.drawLine(275, 350, 275, 0);
// 绘制三球(使用不同颜色区分)
g2d.setColor(Color.CYAN);
g2d.fillOval(x1, y1, 50, 50);
g2d.setColor(Color.GREEN);
g2d.fillOval(x2, y2, 50, 50);
g2d.setColor(Color.RED);
g2d.fillOval(x3, y3, 50, 50);
g2d.dispose(); // ✅ 释放资源
}
private void move() {
x1 += 4; x2 += 4; x3 += 4;
if (x1 > 325) timer.stop();
}
private class MoveListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
move();
repaint(); // 请求重绘,由 Swing 在 EDT 中调度
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(Animation::new); // ✅ 确保 GUI 创建在 EDT
}
}注意事项与最佳实践:
立即学习“Java免费学习笔记(深入)”;
- ✅ 永远不要在 paintComponent 中调用 repaint():这会引发无限递归重绘,导致 CPU 占用飙升;
- ✅ 使用 g.create() + g.dispose():防止抗锯齿等设置污染其他组件的绘图上下文;
- ⚠️ 避免在 paintComponent 中创建对象(如 new Color()):高频调用下易触发 GC,影响动画流畅度;
- ? 若需循环动画,将 timer.restart() 替代 timer.stop(),并在 move() 中添加边界重置逻辑(如 x1 = -50);
- ? 扩展建议:后续可引入 AlphaComposite 实现淡入淡出,或用 BufferedImage 预渲染复杂图形提升性能。
遵循上述规范,即可彻底消除“球体扭曲”现象,获得稳定、清晰、符合 Swing 设计哲学的动画效果。











