Swing中JFrame显示后缩放图像无法显示的技术问题
全屏图像查看器初始化后图像不显示的解决方案
我正在为即时通讯应用开发一款全屏图像查看器,遇到以下问题:JFrame设置为可见后,缩放后的图像无法显示,只有将窗口最小化再恢复后才能看到。调试发现窗口显示后重写的paintComponent()方法甚至未被调用。
原代码
import java.awt.event.KeyEvent; import java.io.IOException; import java.net.MalformedURLException; import javax.swing.*; import java.awt.*; import java.awt.event.KeyListener; import java.awt.image.ImageObserver; import java.net.URL; import javax.imageio.ImageIO; public class Overlay extends JFrame { public Overlay() { super(); this.setUndecorated(true); getRootPane().setOpaque(true); panel = new JPanel(); panel.setBackground(new Color(12,12,12)); setLayout(new CardLayout()); add(panel); panel.setBorder(BorderFactory.createEmptyBorder(90, 90, 90, 90)); this.addKeyListener(new KeyListener() { @Override public void keyTyped(KeyEvent e) {} @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { setVisible(false); dispose(); } } @Override public void keyReleased(KeyEvent e) {} }); } public class ImageViewer extends JPanel { public ImageViewer(String src) { this.src = src; setOpaque(false); try { img = ImageIO.read(new URL(src)); int imgHeight = img.getHeight(observer); if (imgHeight > 0) { w = (int) (h * ((double) img.getWidth(null) / imgHeight)); } } catch (IOException ex) {} } public ImageViewer(String src, int width, int height) { setOpaque(false); setPreferredSize(new Dimension(width, height)); setMinimumSize(new Dimension(width, height)); setMaximumSize(new Dimension(width, height)); w = width; h = height; this.src = src; try { img = ImageIO.read(new URL(src)); int imgHeight = img.getHeight(observer); if (imgHeight > 0) { w = (int) (h * ((double) img.getWidth(null) / imgHeight)); } } catch (IOException ex) {} } ImageObserver observer = new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { if (height == 0) return false; w = (int) (h * ((double) width / height)); if (getParent() != null) getParent().repaint(); return true; } }; @Override public void paintComponent(Graphics g) { super.paintComponent(g); if (img == null) return; Graphics2D g2d = (Graphics2D) g; img.getHeight(observer); int x = (int) (getWidth() - w) / 2; int y = (int) (getHeight() - h) / 2; System.out.println(x + ", " + y); g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g2d.drawImage(img, x, y, w, h, null); } private int w; private int h; private Image img; private String src = null; } public void showContent() { try { GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this); setVisible(true); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { int width = (int) (getBounds().getWidth() * 0.7 + 0.4); int height = (int) (width / 16 * 9 + 0.4); System.err.println(width + "x" + height); ImageViewer viewer = new ImageViewer("http://popov654.pp.ru/copybox/image.jpg", width, height); panel.setLayout(new BorderLayout()); panel.add(viewer); try { Thread.sleep(100); viewer.repaint(); } catch (InterruptedException ex) {} } }); } catch (Exception error) {} } public static void main(String[] args) { new Overlay().showContent(); } JPanel panel; }
问题根源
- UI更新时机错误:窗口已设为可见后才添加
ImageViewer组件,但未通知Swing重新计算布局并刷新界面。 - 阻塞EDT线程:
Thread.sleep(100)会阻塞事件调度线程(EDT),导致UI无法及时响应重绘请求。 - 全屏模式重绘触发缺失:全屏窗口的UI更新需要明确触发布局和重绘操作,否则系统不会自动触发。
修复方案
修改showContent()方法,移除阻塞EDT的代码,并在添加组件后强制触发布局和重绘:
public void showContent() { try { GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { int width = (int) (getBounds().getWidth() * 0.7 + 0.4); int height = (int) (width / 16 * 9 + 0.4); System.err.println(width + "x" + height); ImageViewer viewer = new ImageViewer("http://popov654.pp.ru/copybox/image.jpg", width, height); panel.setLayout(new BorderLayout()); panel.add(viewer); // 通知面板重新计算布局并刷新 panel.revalidate(); panel.repaint(); } }); setVisible(true); } catch (Exception error) {} }
额外优化建议
- 替换KeyListener为KeyBindings:
KeyListener依赖组件焦点,使用KeyBindings可确保ESC键在任何情况下都能触发关闭操作,示例代码:// 在Overlay构造函数中替换KeyListener getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close"); getRootPane().getActionMap().put("close", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { setVisible(false); dispose(); } }); - 异步加载图像:使用
SwingWorker异步加载网络图像,避免阻塞EDT导致UI卡顿。
内容的提问来源于stack exchange,提问作者Alex Popov
相关产品推荐
相关产品推荐

