
在 swing 中实现平滑动画需重写 jpanel 的 paintcomponent() 并调用 super.paintcomponent(g) 清除旧帧;直接重写 jframe 的 paint() 会导致渲染异常、白屏或图像拖影。
Swing 是单线程、双缓冲(默认启用)的 GUI 工具包,其绘图机制严格依赖组件层级和生命周期。错误地重写 JFrame 的 paint() 方法会绕过 Swing 的渲染管线,破坏双缓冲机制,导致旧图形未被清除(出现“拖影”或“畸变”),或因未触发正确的重绘流程而使界面大面积变白。
✅ 正确做法:继承 JPanel,重写 paintComponent()
所有自定义绘制逻辑应封装在 JPanel 子类中,并严格遵循以下三原则:
必须调用 super.paintComponent(g) 作为第一行代码
它负责清空背景、启用双缓冲、准备干净画布。省略此行将残留上一帧内容;错误调用 super.paint() 或 getGraphics().clearRect() 则可能引发线程不安全或渲染冲突。不在顶层窗口(如 JFrame)上直接绘图
JFrame 是容器,本身不参与绘制调度;它的 paint() 方法用于管理子组件布局与装饰(如边框、标题栏),不应承载业务绘图逻辑。确保 GUI 构建与事件调度在 EDT(Event Dispatch Thread)中执行
使用 SwingUtilities.invokeLater() 或 EventQueue.invokeLater() 启动应用,避免多线程并发修改 UI 组件。
以下是修复后的完整可运行示例(已优化结构与健壮性):
立即学习“Java免费学习笔记(深入)”;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Animation extends JPanel {
private static final int PANEL_WIDTH = 500;
private static final int PANEL_HEIGHT = 500;
private static final int BALL_SIZE = 50;
// 小球初始坐标
private int maus1x = 110, maus1y = 350;
private int maus2x = 55, maus2y = 350;
private int maus3x = 0, maus3y = 350;
private final Timer timer;
public Animation() {
setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
setBackground(Color.WHITE); // 显式设置背景色,避免透明干扰
timer = new Timer(100, new TimeListener());
timer.start();
}
@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, PANEL_WIDTH, 400);
g2d.drawLine(0, 350, 225, 350);
g2d.drawLine(275, 350, PANEL_WIDTH, 350);
g2d.drawLine(225, 350, 225, 0);
g2d.drawLine(275, 350, 275, 0);
// 绘制三只小球
g2d.setPaint(Color.CYAN);
g2d.fillOval(maus1x, maus1y, BALL_SIZE, BALL_SIZE);
g2d.setPaint(Color.GREEN);
g2d.fillOval(maus2x, maus2y, BALL_SIZE, BALL_SIZE);
g2d.setPaint(Color.RED);
g2d.fillOval(maus3x, maus3y, BALL_SIZE, BALL_SIZE);
g2d.dispose(); // 释放资源
}
private void move() {
maus1x += 4;
maus2x += 4;
maus3x += 4;
// 停止条件:第一只小球越过终点(x > 325)
if (maus1x > 325) {
timer.stop();
}
}
private class TimeListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
move();
repaint(); // 请求重绘,由 Swing 在 EDT 中异步执行
}
}
// 启动入口
public static void main(String[] args) {
JFrame frame = new JFrame("MausKampf");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(new Animation()); // 添加自定义 JPanel
frame.pack(); // 自动适配尺寸(比 setSize() 更可靠)
frame.setLocationRelativeTo(null); // 居中显示
frame.setVisible(true);
}
}⚠️ 注意事项与最佳实践
- 不要手动调用 repaint() 过于频繁:当前 Timer 间隔为 100ms(10 FPS),对简单动画足够;若需更高帧率(如 60 FPS),建议使用 javax.swing.Timer 而非 java.util.Timer,因其自动在 EDT 中触发。
- 避免在 paintComponent() 中执行耗时操作:如文件读写、网络请求、复杂计算——这会阻塞 EDT,导致界面卡顿。
- 使用 g.create() + g.dispose():防止绘图状态(如颜色、字体、变换)意外影响其他组件。
- 边界检测增强建议:当前仅判断 maus1x > 325,实际中应加入屏幕边界检查(如 maus1x + BALL_SIZE > PANEL_WIDTH),避免小球移出视图后继续计算。
通过遵循 Swing 的绘制规范,你不仅能解决“图形畸变”问题,还能构建出响应迅速、视觉流畅、易于维护的 GUI 动画系统。











