Java Swing 动画中避免图形残影与闪烁的正确绘制方法

浅辰酱_2506

浅辰酱_2506

2026-01-11

939人浏览

原创

Java Swing 动画中避免图形残影与闪烁的正确绘制方法

在 swing 中实现平滑动画需重写 jpanel 的 paintcomponent() 并调用 super.paintcomponent(g) 清除旧帧;直接重写 jframe 的 paint() 会导致渲染异常、白屏或图像拖影。

Swing 是单线程、双缓冲(默认启用)的 GUI 工具包,其绘图机制严格依赖组件层级和生命周期。错误地重写 JFrame 的 paint() 方法会绕过 Swing 的渲染管线,破坏双缓冲机制,导致旧图形未被清除(出现“拖影”或“畸变”),或因未触发正确的重绘流程而使界面大面积变白。

✅ 正确做法:继承 JPanel,重写 paintComponent()

所有自定义绘制逻辑应封装在 JPanel 子类中,并严格遵循以下三原则:

  1. 必须调用 super.paintComponent(g) 作为第一行代码
    它负责清空背景、启用双缓冲、准备干净画布。省略此行将残留上一帧内容;错误调用 super.paint() 或 getGraphics().clearRect() 则可能引发线程不安全或渲染冲突。

  2. 不在顶层窗口(如 JFrame)上直接绘图
    JFrame 是容器,本身不参与绘制调度;它的 paint() 方法用于管理子组件布局与装饰(如边框、标题栏),不应承载业务绘图逻辑。

    JavaScript 测试模式
    JavaScript 测试模式

    使用 Jest、Vitest 和 Testing Library 实施全面的测试策略,包括单元测试、集成测试和端到端测试,涵盖模拟、测试夹具和测试驱动开发。在编

    下载
  3. 确保 GUI 构建与事件调度在 EDT(Event Dispatch Thread)中执行
    使用 SwingUtilities.invokeLater() 或 EventQueue.invokeLater() 启动应用,避免多线程并发修改 UI 组件。

以下是修复后的完整可运行示例(已优化结构与健壮性):

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 动画系统。

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

相关专题

更多
线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

2023.08.10

4898

6

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

2025.12.24

1369

20

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

2026.01.21

463

30

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

2026.01.21

599

24

C# 多线程与异步编程
C# 多线程与异步编程

本专题深入讲解 C# 中多线程与异步编程的核心概念与实战技巧,包括线程池管理、Task 类的使用、async/await 异步编程模式、并发控制与线程同步、死锁与竞态条件的解决方案。通过实际项目,帮助开发者掌握 如何在 C# 中构建高并发、低延迟的异步系统,提升应用性能和响应速度。

2026.02.06

449

20

C++多线程并发控制与线程安全设计实践
C++多线程并发控制与线程安全设计实践

本专题围绕 C++ 在高性能系统开发中的并发控制技术展开,系统讲解多线程编程模型与线程安全设计方法。内容包括互斥锁、读写锁、条件变量、原子操作以及线程池实现机制,同时结合实际案例分析并发竞争、死锁避免与性能优化策略。通过实践讲解,帮助开发者掌握构建稳定高效并发系统的关键技术。

2026.03.16

273

22

Java 多线程与并发编程实战
Java 多线程与并发编程实战

深入讲解 Java 并发编程体系,涵盖 Thread / Runnable / Callable 线程创建方式、线程生命周期与状态转换、synchronized 同步锁与 Lock/ReentrantLock 显式锁、volatile 可见性保证、ThreadPool 线程池配置与调优(核心参数/拒绝策略)、JUC 并发工具类(CountDownLatch/CyclicBarrier/Semaphore/ConcurrentHashMa

2026.04.17

409

31

C++ 多线程使用教程大全
C++ 多线程使用教程大全

系统讲解 C++11 及后续标准引入的多线程编程能力,涵盖 std::thread 线程创建与生命周期管理、std::mutex / shared_mutex 互斥锁与读写锁、std::condition_variable 条件变量同步、std::atomic 原子操作与内存序(memory_order)、std::future / promise / async 异步任务、线程池的设计与实现、死锁检测与避免策略,帮助开发者编写正确高效

2026.04.28

256

17

Python多线程、多进程与并发编程教程大全
Python多线程、多进程与并发编程教程大全

系统讲解 Python 的并发与并行编程体系,涵盖 GIL 全局解释器锁的原理与影响分析、threading 模块的线程创建/锁/事件/信号量、multiprocessing 模块的进程创建/进程间通信(Queue/Pipe/共享内存)、concurrent.futures 线程池与进程池的统一接口、I/O 密集型与 CPU 密集型任务的方案选择、多线程竞态条件排查与线程安全数据结构、subprocess 子进程管理,帮助开发者根据任务

2026.05.08

234

32

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习