預設的 Java Swing 按鈕外觀平庸,尤其是工具欄按鈕,缺乏現代感。若想擁有如 Microsoft Word 工具欄般扁平、滑鼠懸停凸起、按下凹陷的「Cool Button」,其實僅需三個步驟即可輕鬆實現。本文將詳細拆解核心技術:自訂細邊框、滑鼠狀態監聽、按鈕屬性微調,並附上完整原始碼供參考。
JDK 內建的 BevelBorder 線條過粗,無法呈現細緻的工具欄質感。解決方案是繼承 BevelBorder 並覆寫 paintRaisedBevel 與 paintLoweredBevel,僅繪製四條 1 像素邊框線,即可獲得輕薄立體效果。
package com.bhr.ioat.coolbutton;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import javax.swing.border.BevelBorder;
public class ThinBevelBorder extends BevelBorder {
public ThinBevelBorder(int bevelType) { super(bevelType); }
public ThinBevelBorder(int bevelType, Color highlight, Color shadow) { super(bevelType, highlight, shadow); }
public ThinBevelBorder(int bevelType, Color highlightOuter, Color highlightInner, Color shadowOuter, Color shadowInner) {
super(bevelType, highlightOuter, highlightInner, shadowOuter, shadowInner);
}
@Override
protected void paintRaisedBevel(Component c, Graphics g, int x, int y, int w, int h) {
try {
Color old = g.getColor();
g.translate(x, y);
g.setColor(getHighlightInnerColor(c));
g.drawLine(0, 0, 0, h - 1); // 左上亮邊
g.drawLine(1, 0, w - 1, 0); // 頂部亮邊
g.setColor(getShadowInnerColor(c));
g.drawLine(1, h - 1, w - 1, h - 1); // 底部暗邊
g.drawLine(w - 1, 1, w - 1, h - 2); // 右側暗邊
g.translate(-x, -y);
g.setColor(old);
} catch (NullPointerException ignored) {}
}
@Override
protected void paintLoweredBevel(Component c, Graphics g, int x, int y, int w, int h) {
try {
Color old = g.getColor();
g.translate(x, y);
g.setColor(getShadowInnerColor(c));
g.drawLine(0, 0, 0, h - 1);
g.drawLine(1, 0, w - 1, 0);
g.setColor(getHighlightOuterColor(c));
g.drawLine(1, h - 1, w - 1, h - 1);
g.drawLine(w - 1, 1, w - 1, h - 2);
g.translate(-x, -y);
g.setColor(old);
} catch (NullPointerException ignored) {}
}
}
工具欄按鈕需對「滑鼠進入、離開、按下、釋放」四種事件做即時邊框切換。採用單例模式的 MouseAdapter 子類別,靜態常數定義三種邊框狀態:預設空白邊框、懸停凸起邊框、按下凹陷邊框。關鍵邏輯在 mouseEntered:若滑鼠在按下狀態下移入,且為首次按下該按鈕,則顯示凹陷邊框;否則顯示凸起邊框。
package com.bhr.ioat.coolbutton;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractButton;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
public class CoolButtonMouseListener extends MouseAdapter {
public static final Border DEFAULT_BORDER = new EmptyBorder(2, 2, 2, 2);
public static final Border ENTERED_BORDER = new ThinBevelBorder(ThinBevelBorder.RAISED);
public static final Border PRESSED_BORDER = new ThinBevelBorder(ThinBevelBorder.LOWERED);
private static final CoolButtonMouseListener INSTANCE = new CoolButtonMouseListener();
private CoolButtonMouseListener() {}
public static CoolButtonMouseListener getInstance() { return INSTANCE; }
@Override
public void mouseEntered(MouseEvent e) {
AbstractButton btn = (AbstractButton) e.getSource();
if (!btn.isEnabled() || btn.isSelected()) return;
if (btn.getModel().isPressed()) {
btn.setBorder(PRESSED_BORDER);
} else if (e.getModifiers() == MouseEvent.BUTTON1_MASK) {
btn.setBorder(ENTERED_BORDER);
}
}
@Override
public void mouseExited(MouseEvent e) {
AbstractButton btn = (AbstractButton) e.getSource();
if (!btn.isEnabled() || btn.isSelected()) return;
btn.setBorder(DEFAULT_BORDER);
}
@Override
public void mousePressed(MouseEvent e) {
AbstractButton btn = (AbstractButton) e.getSource();
if (!btn.isEnabled() || btn.isSelected()) return;
btn.setBorder(PRESSED_BORDER);
}
@Override
public void mouseReleased(MouseEvent e) {
AbstractButton btn = (AbstractButton) e.getSource();
if (!btn.isEnabled() || btn.isSelected()) return;
btn.setBorder(DEFAULT_BORDER);
}
}
建立 JButton 並套用上述邊框與監聽器後,仍存兩個細節問題:焦點藍框與按下時的深灰背景。解決方法:覆寫 isFocusTraversable() 回傳 false、呼叫 setRequestFocusEnabled(false) 移除焦點框;呼叫 setContentAreaFilled(false) 關閉內容區填充,按下時不再出現灰底。
package com.bhr.ioat.coolbutton;
import javax.swing.*;
import java.awt.FlowLayout;
public class Test extends JFrame {
public Test() {
getContentPane().setLayout(new FlowLayout());
JButton button = new JButton("CoolButton") {
@Override public boolean isFocusTraversable() { return false; }
};
button.setBorder(CoolButtonMouseListener.DEFAULT_BORDER);
button.addMouseListener(CoolButtonMouseListener.getInstance());
button.setRequestFocusEnabled(false);
button.setContentAreaFilled(false);
getContentPane().add(button);
}
public static void main(String[] args) {
JFrame frame = new Test();
frame.setSize(300, 300);
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
CoolButton extends JButton,建構子自動套用邊框、監聽器與屬性,提升復用性。ThinBevelBorder 同樣適用於狀態列、面板等元件,統一視覺風格。