12.2Swing中JButton简单分析

 JButton 的继承结构

public class JButton extends AbstractButton implements Accessible
  • AbstractButton 是所有 Swing 按钮类(如 JToggleButtonJRadioButtonJCheckBox)的基类。
  • 它封装了按钮的核心逻辑:图标、文本、边框、动作事件等。
java.awt.Component
   ↳ java.awt.Container
      ↳ javax.swing.JComponent
         ↳ javax.swing.AbstractButton

1. java.awt.Component

这是 AWT 中最基本的 GUI 类,代表一个可视化的组件(如按钮、文本框等)。

主要功能:
  • 提供绘制能力(paint(Graphics g))
  • 支持布局管理、事件处理(键盘、鼠标等)
  • 控制可见性、大小、位置等属性
常用方法:
  • paint()repaint()
  • setVisible(boolean)
  • setEnabled(boolean)
  • setSize(Dimension)
  • addMouseListener()addKeyListener() 等

2. java.awt.Container

继承自 Component,表示可以包含其他组件的容器。

主要功能:
  • 可以添加子组件(add(Component)
  • 支持布局管理器(LayoutManager)
  • 提供了组件的层次结构支持
常用方法:
  • add(Component comp)
  • remove(Component comp)
  • setLayout(LayoutManager mgr)
  • getComponents()

3. javax.swing.JComponent

Swing 所有可视化组件的基类,是 Container 的子类。

核心特性:
  • 轻量级组件:不依赖本地系统资源,由 Java 自己绘制
  • 双缓冲机制:减少重绘闪烁(默认开启)
  • 可插拔外观与感觉(PLAF):通过 updateUI() 方法切换 L&F
  • 支持边框、工具提示、ActionMap/InputMap 等高级功能
  • 事件监听体系:继承并扩展 AWT 的事件模型
常用方法:
  • setBorder(Border border):设置边框
  • setToolTipText(String text):设置提示文字
  • revalidate()repaint():用于布局更新
  • updateUI():用于切换外观
  • getGraphics():获取图形上下文(不建议直接使用)

AbstractButton 自身的功能扩展

作为 JComponent 的子类,AbstractButton 在其基础上封装了所有按钮共有的行为和状态:

1. 按钮的基本属性

属性说明
text显示的文字
icon显示的图标
rolloverIcon鼠标悬停时显示的图标
pressedIcon按钮按下时显示的图标
disabledIcon不可用时显示的图标
selectedIcon选中状态下显示的图标(适用于 ToggleButton)

2. 按钮的状态

状态说明
armed是否处于“准备触发”状态(鼠标按下)
pressed是否被按下
rollover鼠标是否悬停
selected是否被选中(适用于 JToggleButton)
enabled是否启用

这些状态由 ButtonModel 接口实现类(如 DefaultButtonModel)来维护。


3. 模型对象:ButtonModel

每个 AbstractButton 都关联一个 ButtonModel 实例,它负责管理按钮的状态变化。

public interface ButtonModel {
    boolean isArmed();
    boolean isSelected();
    boolean isEnabled();
    boolean isPressed();
    boolean isRollover();
    void setSelected(boolean b);
    void setEnabled(boolean enabled);
    void addActionListener(ActionListener l);
    ...
}

AbstractButton 使用这个模型来判断当前应该显示什么状态下的图像或样式。


4. 行为与交互

功能描述
action绑定一个 Action 对象,执行动作逻辑
actionCommand触发 ActionEvent 时发送的命令字符串
addActionListener()添加动作监听器,响应点击事件
doClick()模拟按钮点击行为
setMnemonic()设置快捷键(Alt + 字符)
setDisplayedMnemonicIndex()设置快捷键下划线位置

绘图方式:不是直接绘制,而是由 UI Delegate 负责绘制

关键概念:ComponentUI 和 ButtonUI

Swing 使用了一种叫做 UI Delegate 的设计模式,将组件的绘制和交互逻辑委托给特定的 UI 类(这些类通常以 XXXUI 结尾)。

对于 JButton

  • 它的 UI 委托类是 ButtonUI(抽象类)
  • 具体实现根据当前 L&F(LookAndFeel)决定使用哪个子类:
    • MetalButtonUI(默认 Metal 风格)
    • WindowsButtonUI(Windows 外观)
    • GTKButtonUI(Linux/GTK 环境下)
    • AquaButtonUI(macOS)

这些 UI 类负责绘制按钮的外观,包括背景、边框、文字颜色、阴影效果等。

示例代码片段(简化版):

// 在 JButton 初始化时会调用 updateUI() 方法
public void updateUI() {
    setUI((ButtonUI) UIManager.getUI(this));
}

这个方法从 UIManager 获取当前 LookAndFeel 下的 ButtonUI 实现,并设置到按钮上。


绘图流程详解

1. 绘图入口:paint() 方法

所有 Swing 组件的绘制都通过 paint(Graphics g) 方法完成,该方法定义在 AWT 的 Component 类中。

JButton 自己并不重写 paint(),而是继承自 JComponent,最终调用的是其 UI delegate 的绘制方法:

protected void paintComponent(Graphics g) {
    if (ui != null) {
        ui.paint(g, this);
    }
}

所以,实际的绘制工作是在 ButtonUIpaint() 方法中完成的。


2. ButtonUI 的 paint() 方法

比如,在 MetalButtonUI 中,paint() 方法内部会处理:

  • 按钮是否被按下或选中
  • 是否有焦点
  • 边框绘制
  • 渐变背景
  • 文字对齐与渲染

示例伪代码:

public void paint(Graphics g, JComponent c) {
    AbstractButton button = (AbstractButton) c;
    ButtonModel model = button.getModel();

    // 绘制背景
    if (model.isPressed() && model.isArmed()) {
        drawPressedButton(g);
    } else {
        drawNormalButton(g);
    }

    // 绘制文本
    String text = button.getText();
    if (text != null && !text.isEmpty()) {
        paintText(g, button, button.getFont(), text);
    }

    // 绘制图标(如果有)
    Icon icon = button.getIcon();
    if (icon != null) {
        icon.paintIcon(c, g, x, y);
    }
}

跨平台统一的关键:LookAndFeel(L&F)

Swing 提供了 可插拔的外观和感觉机制(PLAF),使得 JButton 可以在不同平台上显示为不同的风格,同时保持一致的行为逻辑。

 支持的 L&F 包括:

L&F 名称描述
Metal默认的 Swing 外观,跨平台统一风格
Windows在 Windows 上模仿本地风格
Nimbus更现代美观的 Swing 风格
GTKLinux 上模仿 GTK 主题风格
AquamacOS 上模仿原生 Mac 风格

 设置 LookAndFeel 示例:

try {
    UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
} catch (Exception e) {
    e.printStackTrace();
}

总结:JButton 如何实现自己的绘制 + 跨平台统一?

特性实现方式
是否自己绘制?❌ 不直接绘制,而是通过 ButtonUI 子类绘制
绘制入口?paintComponent(Graphics g) → ButtonUI.paint()
绘制内容?文本、图标、边框、状态变化(如按下、聚焦)
跨平台关键?通过 PLAF(LookAndFeel)机制选择不同的 ButtonUI 实现
优点外观统一、支持换肤、易于扩展
缺点性能略低于本地控件(但差异不大)

示例:在同一个窗口上绘制两个“按钮” 

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class CustomButtonDemo extends JFrame {

    private Rectangle button1 = new Rectangle(50, 50, 120, 40);
    private Rectangle button2 = new Rectangle(50, 120, 120, 40);

    public CustomButtonDemo() {
        setTitle("Java 2D 自定义控件示例");
        setSize(300, 250);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        add(new DrawingPanel());
        setLocationRelativeTo(null); // 居中显示
        setVisible(true);
    }

    class DrawingPanel extends JPanel {

        private boolean isButton1Pressed = false;
        private boolean isButton2Pressed = false;

        public DrawingPanel() {
            // 添加鼠标监听器来检测点击
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    if (button1.contains(e.getPoint())) {
                        isButton1Pressed = true;
                        repaint();
                    } else if (button2.contains(e.getPoint())) {
                        isButton2Pressed = true;
                        repaint();
                    }
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    if (isButton1Pressed && button1.contains(e.getPoint())) {
                        System.out.println("你点击了按钮 1");
                    }
                    if (isButton2Pressed && button2.contains(e.getPoint())) {
                        System.out.println("你点击了按钮 2");
                    }
                    isButton1Pressed = false;
                    isButton2Pressed = false;
                    repaint();
                }
            });
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;

            // 设置抗锯齿
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                 RenderingHints.VALUE_ANTIALIAS_ON);

            // 绘制按钮 1
            if (isButton1Pressed) {
                g2d.setColor(Color.LIGHT_GRAY);
            } else {
                g2d.setColor(Color.GRAY);
            }
            g2d.fill(button1);
            g2d.setColor(Color.BLACK);
            g2d.draw(button1);
            drawCenteredString(g2d, "按钮 1", button1);

            // 绘制按钮 2
            if (isButton2Pressed) {
                g2d.setColor(Color.LIGHT_GRAY);
            } else {
                g2d.setColor(Color.GRAY);
            }
            g2d.fill(button2);
            g2d.setColor(Color.BLACK);
            g2d.draw(button2);
            drawCenteredString(g2d, "按钮 2", button2);
        }

        // 在矩形中间画字符串
        private void drawCenteredString(Graphics2D g, String text, Rectangle rect) {
            FontMetrics fm = g.getFontMetrics();
            int x = rect.x + (rect.width - fm.stringWidth(text)) / 2;
            int y = rect.y + ((rect.height - fm.getHeight()) / 2) + fm.getAscent();
            g.drawString(text, x, y);
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(CustomButtonDemo::new);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chxii

小小打赏,大大鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值