修改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
相关产品推荐
相关产品推荐

