Swing自定义UI组件绘制异常:仅首个JRadioButton正常显示
问题描述
开发侧边栏时使用ButtonGroup管理JRadioButton,通过自定义BasicRadioButtonUI实现样式定制。当前遇到以下问题:
- 首个应用自定义UI的按钮能正常绘制,其余按钮无法正常显示
- 所有按钮的
paintComponent()方法均被调用(控制台有输出) - 尝试过继承
JRadioButton重写paintComponent(),遇到完全相同的问题 - 无论是用IntelliJ Swing GUI Designer创建面板,还是普通
JPanel,问题都能复现
用户提供的代码如下:
自定义UI类
public class Side_Bar_Button_UI extends BasicRadioButtonUI { private static final Side_Bar_Button_UI INSTANCE = new Side_Bar_Button_UI(); private static final int padding = 6; private static final int padding_d = 12; private static final int arc = 8; public static ComponentUI createUI(JComponent c) { return INSTANCE; } @Override protected void installDefaults(AbstractButton b) { super.installDefaults(b); b.setBorder(new EmptyBorder(padding, padding, padding, padding)); } @Override public synchronized void paint(Graphics g, JComponent c) { super.paint(g, c); paintComponent(g, c); } private void paintComponent(Graphics g, JComponent c) { System.out.println("paintComponent called on" + c); Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int x = c.getX(); int y = c.getY(); int w = c.getWidth(); int h = c.getHeight(); // Paint background g2.setColor(Colors.SIDE_PANEL); g2.fillRect(x, y, w, h); JRadioButton b = (JRadioButton) c; // Paint focus if selected if (b.isSelected()) { g2.setColor(Colors.SIDE_PANEL_SELECTED); g2.fillRoundRect(x+padding, y+padding, w-padding_d, h-padding_d, arc, arc); } // Paint icon Icon icon = b.getIcon(); if (icon != null) { icon.paintIcon(c, g2, x+padding_d, y+padding_d); } // Paint text String text = b.getText(); if (text != null && !text.isEmpty()) { FontMetrics fm = g2.getFontMetrics(); int text_height = fm.getHeight(); g2.setColor(b.getForeground()); g2.drawString(text, x + padding_d + icon.getIconWidth() + padding, y + h / 2 + text_height / 4); } g2.dispose(); } @Override protected void paintFocus(Graphics g, Rectangle textRect, Dimension size) { // Removing focus paint } }
页面类
public class Admin_Page { private JPanel page; private JPanel top_panel; private JPanel side_panel; private JRadioButton side_bar_button_1; private JRadioButton side_bar_button_2; private JRadioButton side_bar_button_3; private JRadioButton side_bar_button_4; private JButton button1; private JPanel side_bar_button_panel; private ButtonGroup side_bar_button_group; public Admin_Page() { // createUIComponents() automatically called } public JPanel get_page() { return page; } private void createUIComponents() { side_bar_button_1 = new JRadioButton(); side_bar_button_1.setUI(new Side_Bar_Button_UI()); side_bar_button_1.setIcon(new ImageIcon("resources/icons/dashboard_icon_24x24.png")); side_bar_button_2 = new JRadioButton(); side_bar_button_2.setUI(new Side_Bar_Button_UI()); side_bar_button_2.setIcon(new ImageIcon("resources/icons/category_icon_24x24.png")); side_bar_button_3 = new JRadioButton(); side_bar_button_3.setUI(new Side_Bar_Button_UI()); side_bar_button_3.setIcon(new ImageIcon("resources/icons/trolley_icon_24x24.png")); side_bar_button_4 = new JRadioButton(); side_bar_button_4.setUI(new Side_Bar_Button_UI()); side_bar_button_4.setIcon(new ImageIcon("resources/icons/profiles_icon_24x24.png")); } }
问题原因与修复方案
1. 单例UI实例的冲突
你的Side_Bar_Button_UI使用了静态单例实例,但Swing的UI组件设计要求每个控件可以拥有独立的UI实例(确保状态隔离)。单例会导致多个按钮共享同一个UI对象,引发绘制状态混乱。
修改createUI方法,每次返回新的UI实例:
public static ComponentUI createUI(JComponent c) { return new Side_Bar_Button_UI(); }
2. 错误的坐标系使用
Swing组件的Graphics上下文是相对于自身的局部坐标系(左上角为(0,0)),但你使用了c.getX()和c.getY()(组件在父容器中的全局坐标)作为绘制起点,导致后续按钮的绘制内容超出自身可视范围。
修正坐标计算:
// 替换原有x、y的赋值 int x = 0; int y = 0; int w = c.getWidth(); int h = c.getHeight();
3. 重复绘制与Graphics资源滥用
- 重写
paint方法同时调用super.paint和自定义paintComponent,导致重复绘制,覆盖了后续按钮的内容。 - 手动调用
g2.dispose()会释放Swing管理的Graphics资源,导致后续绘制失效。Swing会自动管理该资源,无需手动释放。
修复方案:
- 移除自定义的
paint方法,直接重写BasicRadioButtonUI的paintComponent方法 - 创建
Graphics副本进行绘制,避免影响原资源 - 仅释放自己创建的
Graphics副本
4. 取消默认图标绘制
默认的BasicRadioButtonUI会绘制单选框图标,和自定义图标冲突,需要重写paintIcon方法取消默认绘制。
最终修复后的完整UI类
public class Side_Bar_Button_UI extends BasicRadioButtonUI { private static final int padding = 6; private static final int padding_d = 12; private static final int arc = 8; public static ComponentUI createUI(JComponent c) { return new Side_Bar_Button_UI(); } @Override protected void installDefaults(AbstractButton b) { super.installDefaults(b); b.setBorder(new EmptyBorder(padding, padding, padding, padding)); // 取消默认图标,避免和自定义绘制冲突 b.setIcon(null); b.setSelectedIcon(null); } @Override protected void paintComponent(Graphics g, JComponent c) { System.out.println("paintComponent called on" + c); // 创建Graphics副本,不影响原资源 Graphics2D g2 = (Graphics2D) g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int x = 0; int y = 0; int w = c.getWidth(); int h = c.getHeight(); // 绘制背景 g2.setColor(Colors.SIDE_PANEL); g2.fillRect(x, y, w, h); JRadioButton b = (JRadioButton) c; // 绘制选中状态背景 if (b.isSelected()) { g2.setColor(Colors.SIDE_PANEL_SELECTED); g2.fillRoundRect(x+padding, y+padding, w-padding_d, h-padding_d, arc, arc); } // 绘制自定义图标 Icon icon = b.getIcon(); if (icon != null) { icon.paintIcon(c, g2, x+padding_d, y+padding_d); } // 绘制文本 String text = b.getText(); if (text != null && !text.isEmpty()) { FontMetrics fm = g2.getFontMetrics(); int text_height = fm.getHeight(); g2.setColor(b.getForeground()); g2.drawString(text, x + padding_d + icon.getIconWidth() + padding, y + h / 2 + text_height / 4); } // 仅释放自己创建的Graphics副本 g2.dispose(); } @Override protected void paintFocus(Graphics g, Rectangle textRect, Dimension size) { // 不绘制焦点框 } @Override protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect) { // 取消默认单选框图标绘制 } }
额外建议
- 确保
Colors.SIDE_PANEL和Colors.SIDE_PANEL_SELECTED是与父容器背景不同的颜色,避免内容被遮挡 - 给按钮设置文本(如
side_bar_button_1.setText("Dashboard")),测试文本绘制是否正常
内容的提问来源于stack exchange,提问作者gocode_
相关产品推荐
相关产品推荐

