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

修改loading属性后如何强制调用JLayerUI.paint方法?调用JLayer.repaint()无效的问题

问题根源分析

你的问题核心在于违反了Swing的单线程模型:你把耗时的Thread.sleep(6000)操作放在了事件调度线程(EDT)中执行,导致EDT被阻塞,无法处理重绘请求。

当你调用layer.repaint()时,只是把重绘任务加入了EDT的任务队列,但此时EDT正被Thread.sleep占用,根本没有机会去执行这个重绘任务。直到6秒的sleep结束后,EDT才会处理队列里的任务,但这时候你已经把loading设为false了,所以加载提示自然不会显示。

解决方案:使用后台线程处理耗时任务

Swing提供了SwingWorker类,专门用来处理后台耗时任务,同时能安全地更新UI。我们需要把耗时的加载逻辑放到后台线程中,让EDT保持空闲来处理UI更新(比如重绘)。

以下是修正后的完整代码:

import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayer;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.plaf.LayerUI;

public class Test extends JFrame {
    JPanel panel;
    LoadingLayerUI layerUI;
    JLayer<JPanel> layer;

    public Test() {
        panel = new JPanel();
        layerUI = new LoadingLayerUI();
        layer = new JLayer<>(panel, layerUI);
        add(layer, BorderLayout.CENTER);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(new Dimension(500, 500));
    }

    private void loadPreview() {
        SwingUtilities.invokeLater(() -> {
            // 开启加载状态,此时EDT空闲,能立即处理重绘
            layerUI.activate(true);
            layer.repaint();

            // 使用SwingWorker执行后台耗时任务
            new SwingWorker<Void, Void>() {
                @Override
                protected Void doInBackground() throws Exception {
                    // 这里执行耗时的加载操作,不在EDT中
                    Thread.sleep(6000);
                    return null;
                }

                @Override
                protected void done() {
                    // 任务完成后,在EDT中更新UI状态
                    layerUI.activate(false);
                    layer.repaint();
                }
            }.execute();
        });
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            Test test = new Test();
            test.setVisible(true);
            test.loadPreview();
        });
    }
}

class LoadingLayerUI extends LayerUI<JPanel> {
    private boolean loading;

    public void activate(boolean enabled) {
        loading = enabled;
    }

    @Override
    public void paint(Graphics g, JComponent c) {
        super.paint(g, c);
        if (!loading) {
            return;
        }
        Graphics2D g2 = (Graphics2D) g.create();
        int w = c.getWidth();
        int h = c.getHeight();
        g2.setComposite(AlphaComposite.getInstance(
                AlphaComposite.SRC_OVER, .5f));
        g2.setColor(new Color(53, 146, 196));
        g2.fillRect(0, 0, w, h);
        g2.setColor(Color.WHITE);
        g2.drawString("Loading...", 100, 100);
        g2.dispose();
    }
}
关键改动说明
  • 把耗时的加载逻辑移到SwingWorker的doInBackground方法中,这个方法是在后台线程执行的,不会阻塞EDT。
  • 开启加载状态的操作(layerUI.activate(true)和layer.repaint())直接放在EDT中执行,此时EDT空闲,能立即处理重绘请求,显示加载提示。
  • 任务完成后,done方法会自动在EDT中执行,我们在这里关闭加载状态并触发重绘,保证UI更新的线程安全性。
重要提醒

Swing的所有UI操作(包括组件的创建、更新、重绘)都必须在EDT中执行,但绝对不能在EDT中执行任何耗时操作(比如网络请求、文件IO、长时间计算),否则会导致UI冻结、无响应,就像你遇到的问题一样。始终用后台线程处理耗时任务,再通过SwingUtilities.invokeLater或者SwingWorker的done/process方法来更新UI。

内容的提问来源于stack exchange,提问作者Naruto Biju Mode

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 14:13:13