JButton
的继承结构
public class JButton extends AbstractButton implements Accessible
AbstractButton
是所有 Swing 按钮类(如JToggleButton
,JRadioButton
,JCheckBox
)的基类。- 它封装了按钮的核心逻辑:图标、文本、边框、动作事件等。
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);
}
}
所以,实际的绘制工作是在 ButtonUI
的 paint()
方法中完成的。
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 风格 |
GTK | Linux 上模仿 GTK 主题风格 |
Aqua | macOS 上模仿原生 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);
}
}