Java Swing JLabel重复绘制问题:窗口最小化恢复后显示正常
问题现象
开发学校Java游戏的机制说明窗口时,用于展示文本的JLabel出现重复绘制异常,窗口最小化再恢复后显示恢复正常。为实现文本换行,JLabel使用了HTML格式,不确定是否为问题根源,也不清楚排查方向。
问题代码
int panelWidth = 16*16*3 + 300 + 3; int panelHeight = 16*13*3; JPanel textPanel = new JPanel(); textPanel.setLayout(null); textPanel.setBounds(0, 0, 2*panelWidth/5, panelHeight); textPanel.setOpaque(true); textPanel.setBackground(new Color(0,0,0,0)); JPanel bilderPanel = new JPanel(null){ private static final long serialVersionUID = 1L; @Override protected void paintComponent(Graphics g) { super.paintComponent(g); BufferedImage background = null; BufferedImage image1 = null; BufferedImage image2 = null; try { background = ImageIO.read(getClass().getResource("/Screens/ShovelKnightSplash.png")); image1 = ImageIO.read(getClass().getResource("/Screens/ShovelKnightSplash.png")); image2 = ImageIO.read(getClass().getResource("/Screens/ShovelKnightSplash.png")); } catch (IOException e) { e.printStackTrace(); } int margin = 15; int withPicture = 3*panelWidth/5 - 2*margin; int heightPicture = panelHeight /2 - 2*margin; g.drawImage(image1, margin/2, 0*heightPicture + 1*margin/2, withPicture, heightPicture, null); g.drawImage(image2, margin/2, 1*heightPicture + 2*margin/2, withPicture, heightPicture, null); } }; bilderPanel.setBounds(2*panelWidth/5, 0, 3*panelWidth/5, panelHeight); bilderPanel.setOpaque(true); bilderPanel.setBackground(new Color(0,0,0,0)); panel.add(textPanel); panel.add(bilderPanel); JLabel Uberschrift = new JLabel(); Uberschrift.setText("Das Spiel"); Uberschrift.setBounds(25, 20, 200, 50); Uberschrift.setFont(new Font("Arial", Font.PLAIN, 25)); JLabel Controls = new JLabel(); Controls.setText("<html><body>" + "Controls:<br>" + "W,A,S,D -> Bewegen<br>" + "Leertaste -> Menü Öffnen<br>" + "</body></html>"); Controls.setBounds(25, panelHeight - 250, 2*panelWidth/5 - 50, 150); Controls.setFont(new Font("Arial", Font.PLAIN, 18)); JLabel text = new JLabel(); text.setText("<html><main>" + "Die Geschichte spielt in einer offenen Welt, " + "doch neben Ruinen gibt es auch mehrere intakte und bewohnte Städte. Die Welt " + "umfasst verschiedene Landschaftstypen wie zum Beispiel weite Wiesen, Wälder, " + "Berge und Wüsten." + "Von Monstern befallen, ist es die Aufgabe von dir" + "und deinem Partner die einzelnen Städte zurückzu erobern" + "" + "" + "Es gibt 5 MiniBosse und einen Endboss, welcher sich in der Mitte der Welt befindet" + "Man kann ihn bekämpfen, wann man will. Man ist frei in seinem tun." + "Allerdings erleichtert es einem das Spielgeschehen, wenn man sich zuvor mit Items aufrüstet" + "und LVL sammelt. Für jeden besiegten MiniBoss wird der Endeboss zunehmend geschwächt." + "</main></html>"); text.setBounds(25, 10, 2*panelWidth/5 - 50, 400); text.setFont(new Font("Arial", Font.PLAIN, 15)); textPanel.add(Uberschrift); textPanel.add(Controls); textPanel.add(text);
问题根源分析
透明组件的Opaque属性冲突:
textPanel和bilderPanel设置了setOpaque(true)但背景是完全透明的new Color(0,0,0,0)。Swing中setOpaque(true)要求组件完全填充自身背景,透明背景与该属性搭配会导致Swing无法正确清理组件区域,重绘时残留之前的绘制内容,引发重复绘制。Null布局导致布局管理混乱:
手动使用setLayout(null)和bounds定位组件,Swing无法自动计算组件的位置与大小,重绘时无法准确确定刷新区域,容易出现组件重叠、绘制残留。paintComponent中重复加载图片:
每次重绘都通过ImageIO.read加载图片,IO操作会导致绘制延迟,甚至因资源加载不及时引发绘制异常。JLabel区域重叠:
标题Uberschrift的y坐标为20,文本text的y坐标为10,两者区域重叠,加上背景未正确清理,重绘时会出现文本叠加重复。
修复方案
1. 修正透明组件的Opaque设置
将透明背景的面板设置为setOpaque(false),让Swing正确处理背景绘制:
textPanel.setOpaque(false); textPanel.setBackground(new Color(0,0,0,0)); bilderPanel.setOpaque(false); bilderPanel.setBackground(new Color(0,0,0,0));
2. 替换Null布局为标准布局管理器
推荐使用BoxLayout管理textPanel的组件,避免手动设置bounds,示例如下:
textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.Y_AXIS)); // 添加顶部间距 textPanel.add(Box.createVerticalStrut(20)); // 标题组件 JLabel Uberschrift = new JLabel("Das Spiel"); Uberschrift.setFont(new Font("Arial", Font.PLAIN, 25)); Uberschrift.setAlignmentX(Component.LEFT_ALIGNMENT); textPanel.add(Uberschrift); // 添加标题与文本的间距 textPanel.add(Box.createVerticalStrut(30)); // 大段文本组件 JLabel text = new JLabel("<html><main>...</main></html>"); text.setFont(new Font("Arial", Font.PLAIN, 15)); text.setAlignmentX(Component.LEFT_ALIGNMENT); // 限制宽度避免文本过宽 text.setMaximumSize(new Dimension(2*panelWidth/5 - 50, Integer.MAX_VALUE)); textPanel.add(text); // 填充空白区域,将控制说明推到底部 textPanel.add(Box.createVerticalGlue()); // 控制说明组件 JLabel Controls = new JLabel("<html><body>Controls:<br>...</body></html>"); Controls.setFont(new Font("Arial", Font.PLAIN, 18)); Controls.setAlignmentX(Component.LEFT_ALIGNMENT); textPanel.add(Controls); // 添加底部间距 textPanel.add(Box.createVerticalStrut(25));
3. 预加载图片
将图片加载逻辑移到paintComponent外部,在类构造方法中加载一次,避免每次重绘都读取资源:
public class BilderPanel extends JPanel { private static final long serialVersionUID = 1L; private BufferedImage image1; private BufferedImage image2; private int panelWidth; private int panelHeight; public BilderPanel(int panelWidth, int panelHeight) { super(null); this.panelWidth = panelWidth; this.panelHeight = panelHeight; try { image1 = ImageIO.read(getClass().getResource("/Screens/ShovelKnightSplash.png")); image2 = ImageIO.read(getClass().getResource("/Screens/ShovelKnightSplash.png")); } catch (IOException e) { e.printStackTrace(); } setBounds(2*panelWidth/5, 0, 3*panelWidth/5, panelHeight); setOpaque(false); setBackground(new Color(0,0,0,0)); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (image1 == null || image2 == null) return; int margin = 15; int withPicture = 3*panelWidth/5 - 2*margin; int heightPicture = panelHeight /2 - 2*margin; g.drawImage(image1, margin/2, 0*heightPicture + 1*margin/2, withPicture, heightPicture, null); g.drawImage(image2, margin/2, 1*heightPicture + 2*margin/2, withPicture, heightPicture, null); } }
4. 避免组件区域重叠
使用标准布局管理器后,组件位置由Swing自动计算,无需手动设置bounds,从根源避免重叠问题。
内容的提问来源于stack exchange,提问作者Hemsho

