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

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_

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 00:08:10