You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为JLabel设置ZOrder导致JFrame中组件闪烁的问题排查

Java Swing 2D游戏闪烁问题解决

问题根源

你的代码里导致闪烁的核心问题不是setComponentZOrder,而是以下几个关键错误:

  • 主线程阻塞:用while(close == false)循环在主线程跑游戏逻辑,完全阻塞了Swing的事件调度线程(EDT),组件重绘、事件处理都被打乱,必然出现闪烁。
  • 组件无限堆积:加载动画时每次都新建JLabel并添加到窗口,旧的组件没有移除,容器里的组件越来越多,重绘时需要处理大量元素,导致卡顿和闪烁。
  • 频繁无意义重绘:循环里反复调用revalidate()和repaint(),加重了EDT的负担。
  • Null布局的低效:手动设置setBounds的null布局,Swing无法对组件重绘做优化,也是闪烁的诱因之一。

修复方案

1. 用Swing Timer替代无限循环

Swing Timer是专门用于EDT中执行定时任务的工具,不会阻塞线程,能保证UI更新在正确的线程中进行。

2. 复用组件而非重复创建

动画元素只创建一次,每次更新时替换ImageIcon即可,避免组件堆积。

3. 启用全局双缓冲

给JFrame的内容面板启用双缓冲,减少闪烁。

4. 优化ZOrder使用

确保层级设置只在组件初始化时做一次,不要频繁修改。

5. 避免手动调用revalidate/repaint

组件属性变更后Swing会自动触发重绘,除非必要不要手动调用。

修改后的完整代码

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;
import java.util.*;

public class World implements KeyListener {
    protected JFrame console = new JFrame("Tower of Fiction");
    protected ArrayList<JLabel> displayObjects = new ArrayList<>();
    // 复用的加载动画标签
    private JLabel loadingLabel;
    private int ticker2 = 21;
    private Timer loadingTimer;
    private boolean close = false;
    private boolean screen2 = false;
    private int menuOption = 0;

    public World() throws IOException, FontFormatException {
        console.setSize(1920, 1080);
        console.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        console.setLayout(null);
        console.addKeyListener(this);
        // 启用全局双缓冲
        console.getContentPane().setDoubleBuffered(true);

        // 初始化菜单文本
        drawText("Start Game", "BLKCHCRY.TTF", 100f, 201, 193, 59, 750, 440, 1);
        drawText("Settings", "BLKCHCRY.TTF", 100f, 201, 193, 59, 828, 605, 1);
        displayObjects.get(1).setVisible(false);
        drawText("Quit Game", "BLKCHCRY.TTF", 100f, 201, 193, 59, 762, 775, 1);
        displayObjects.get(2).setVisible(false);
        // 背景图放在最底层(order=0)
        drawImage("Animations and Images/Images/PNG/Fictionville Home Screen (First Time).png", 0, 0, 0);

        // 初始化加载动画标签(先隐藏)
        loadingLabel = new JLabel();
        loadingLabel.setBounds(0, 0, 1920, 1080);
        console.add(loadingLabel);
        console.setComponentZOrder(loadingLabel, 1); // 加载动画放在最上层
        loadingLabel.setVisible(false);

        console.setVisible(true);
    }

    // 绘制图片(复用逻辑)
    public void drawImage(String imagePath, int xCord, int yCord, int order) throws IOException {
        BufferedImage image = ImageIO.read(new File(imagePath));
        JLabel asset = new JLabel(new ImageIcon(image));
        Dimension size = asset.getPreferredSize();
        asset.setBounds(xCord, yCord, size.width, size.height);
        console.add(asset);
        console.setComponentZOrder(asset, order);
        displayObjects.add(asset);
    }

    // 绘制文本
    public void drawText(String text, String fontPath, float size, int R, int G, int B, int xCord, int yCord, int order)
            throws FontFormatException, IOException {
        JLabel label = new JLabel(text);
        Font fontStyle = Font.createFont(Font.TRUETYPE_FONT, getClass().getResourceAsStream(fontPath)).deriveFont(size);
        GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(fontStyle);

        label.setForeground(new Color(R, G, B));
        label.setFont(fontStyle);
        Dimension sizeOfWords = label.getPreferredSize();
        label.setBounds(xCord, yCord, sizeOfWords.width, sizeOfWords.height);

        console.add(label);
        console.setComponentZOrder(label, order);
        displayObjects.add(label);
    }

    // 启动加载动画
    private void startLoadingAnimation() {
        ticker2 = 21;
        loadingLabel.setVisible(true);
        // 用Swing Timer定时更新动画
        loadingTimer = new Timer(100, e -> {
            try {
                BufferedImage frame = ImageIO.read(new File(
                        "Animations and Images/Animations/GIF/Load Screen Animation/Loading Screen" + ticker2 + ".png"));
                loadingLabel.setIcon(new ImageIcon(frame));
                ticker2--;
                if (ticker2 == 0) {
                    loadingTimer.stop();
                    loadingLabel.setVisible(false);
                    screen2 = false;
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        });
        loadingTimer.start();
    }

    @Override
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();

        if (!screen2) {
            if (keyCode == KeyEvent.VK_DOWN) {
                displayObjects.get(menuOption).setVisible(false);
                menuOption = menuOption == 2 ? 0 : menuOption + 1;
                displayObjects.get(menuOption).setVisible(true);
            } else if (keyCode == KeyEvent.VK_UP) {
                displayObjects.get(menuOption).setVisible(false);
                menuOption = menuOption == 0 ? 2 : menuOption - 1;
                displayObjects.get(menuOption).setVisible(true);
            }

            if (menuOption == 2 && keyCode == KeyEvent.VK_ENTER) {
                close = true;
                console.dispose();
            }
        }

        if (menuOption == 0 && (keyCode == KeyEvent.VK_SPACE || keyCode == KeyEvent.VK_ENTER)) {
            if (!screen2) {
                screen2 = true;
                startLoadingAnimation();
            }
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {}

    @Override
    public void keyTyped(KeyEvent e) {}

    public static void main(String[] args) {
        // 确保UI初始化在EDT中
        SwingUtilities.invokeLater(() -> {
            try {
                new World();
            } catch (IOException | FontFormatException ex) {
                ex.printStackTrace();
            }
        });
    }
}

额外注意事项

  • 所有Swing组件的创建和更新都要放在EDT中,用SwingUtilities.invokeLater()包裹初始化代码是标准做法。
  • 尽量避免使用null布局,复杂UI可以用OverlayLayout或者CardLayout来管理层级和页面切换,比手动设置setBounds高效得多。
  • 如果后续游戏逻辑复杂,建议自定义JPanel并重写paintComponent()方法来绘制所有游戏元素,这是Swing开发游戏的标准模式,比用JLabel组件更灵活高效。

内容的提问来源于stack exchange,提问作者asdadada

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 21:54:52