過去要自訂 Swing 外觀,需繼承 Metal 39 個類或 Basic 60 個類,工作量龐大、程式碼混亂、文檔缺乏,導致開源客製外觀鳳毛麟角。Java 5.0 引入 Synth Look and Feel,以「皮膚」概念徹底改變遊戲規則:**零 Java 代碼編寫外觀,完全透過 XML + 圖片資源描述**,設計師與開發者完美分工。
SynthLookAndFeel synth = new SynthLookAndFeel();
synth.load(MyFrame.class.getResourceAsStream("demo.xml"), MyFrame.class);
UIManager.setLookAndFeel(synth);
核心哲學:Synth 是「白板」外觀——預設完全空白,只有在 XML 定義元件時才會渲染。
包含顏色、字體、圖片、狀態、元件專屬屬性。建議**每個 Swing 元件一個 style**,雖不利用 XML 繼承,但最易編寫、除錯、維護。
<bind style="button" type="region" key="Button"/>
key 對應 javax.swing.plaf.synth.Region 常數(如 Button、TextField、ScrollBarThumb)JTabbedPane、JScrollBar)擁有多個 Region<style id="default">
<font name="Aharoni" size="14"/>
<state>
<color value="#FFFFFF" type="FOREGROUND"/>
</state>
</style>
<bind style="default" type="region" key=".*"/> <!-- 萬用字元套用所有元件 -->
痛點:TextField 邊框非固定尺寸,如何用單一圖片適應任意大小?
解法:九宮格切圖——將圖片切為 9 區域(四角不拉伸、邊緣單向拉伸、中心可選),透過 sourceInsets 定義切割線。
<style id="textfield">
<opaque value="true"/>
<state>
<font name="Aharoni" size="14"/>
<color value="#D2DFF2" type="BACKGROUND"/>
<color value="#000000" type="TEXT_FOREGROUND"/>
</state>
<imagePainter method="textFieldBorder" path="images/textfield.png"
sourceInsets="4 6 4 6" paintCenter="false"/>
<insets top="4" left="6" bottom="4" right="6"/> <!-- 增加內邊距容納圖片 -->
</style>
<bind style="textfield" type="region" key="TextField"/>
method 對應 SynthPainter.paintTextFieldBorder(去掉 paint 前綴、首字母小寫)sourceInsets:上 左 下 右(像素)paintCenter="false":中心區域留給文字渲染<style id="button">
<state> <!-- DEFAULT 狀態 -->
<imagePainter method="buttonBackground" path="images/button.png"
sourceInsets="9 10 9 12" paintCenter="true" stretch="true"/>
<insets top="9" left="10" bottom="9" right="12"/>
<font name="Aharoni" size="16"/>
<color type="TEXT_FOREGROUND" value="#FFFFFF"/>
</state>
<state value="MOUSE_OVER">
<imagePainter method="buttonBackground" path="images/button_on.png" .../>
<insets .../>
<color type="TEXT_FOREGROUND" value="#FFFFFF"/>
</state>
<state value="PRESSED">
<imagePainter method="buttonBackground" path="images/button_press.png" .../>
<insets .../>
<color type="TEXT_FOREGROUND" value="#FFFFFF"/>
</state>
<property key="Button.margin" type="insets" value="0 0 0 0"/>
</style>
<bind style="button" type="region" key="Button"/>
支援狀態:ENABLED、MOUSE_OVER、PRESSED、DISABLED、FOCUSED、SELECTED、DEFAULT。可組合:value="MOUSE_OVER and PRESSED"。
<style id="checkbox">
<imageIcon id="check_off" path="images/checkbox_off.png"/>
<imageIcon id="check_on" path="images/checkbox_on.png"/>
<property key="CheckBox.icon" value="check_off"/>
<state value="SELECTED">
<property key="CheckBox.icon" value="check_on"/>
</state>
</style>
<bind style="checkbox" type="region" key="CheckBox"/>
<property> 如雜湊表,直接對應 UIManager 鍵值對。
XML 無法描述複雜繪圖邏輯時,擴展 SynthPainter 並透過 <painter> 註冊。
<style id="panel">
<object id="background" class="demo.synth.BackgroundPainter"/>
<object class="javax.swing.plaf.ColorUIResource" id="startColor">
<int>30</int><int>123</int><int>235</int>
</object>
<defaultsProperty key="Panel.startBackground" type="idref" value="startColor"/>
<object class="javax.swing.plaf.ColorUIResource" id="endColor">
<int>1</int><int>20</int><int>80</int>
</object>
<defaultsProperty key="Panel.endBackground" type="idref" value="endColor"/>
<painter method="panelBackground" idref="background"/>
</style>
<bind style="panel" type="region" key="Panel"/>
public class BackgroundPainter extends SynthPainter {
public void paintPanelBackground(SynthContext ctx, Graphics g, int x, int y, int w, int h) {
Color start = UIManager.getColor("Panel.startBackground");
Color end = UIManager.getColor("Panel.endBackground");
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(new GradientPaint(x, y, start, w, h, end));
g2.fillRect(x, y, w, h);
g2.setColor(new Color(255, 255, 255, 120));
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.draw(new CubicCurve2D.Double(0, h/4, w/3, h/10, 66*w, 1.5*h, w, h/8));
}
}
<bind style="mystyle" type="name" key="Custom.*"/> <!-- 針對類名開頭為 Custom 的元件 -->
<style id="base">
<color value="BLACK" type="BACKGROUND"/>
<state><font size="14"/></state>
</style>
<bind style="base" type="region" key=".*"/>
<style id="sublevel" clone="base">
<color value="RED" type="BACKGROUND"/> <!-- 覆寫背景色 -->
</style>
<bind style="sublevel" type="region" key="Label"/>
| 機器配置 | Ocean | Motif | Synth |
|---|---|---|---|
| Win XP 1.7GHz 2GB | 0.32s | 0.29s | 0.57s |
| SuSE 3.3GHz 2GB | 0.23s | 0.20s | 0.45s |
| Red Hat 1.4GHz 512MB | 0.37s | 0.32s | 0.61s |
| 機器配置 (100 元件壓力測試) | Ocean | Motif | Synth |
|---|---|---|---|
| Win XP | 0.33s | 0.32s | 0.34s |
| SuSE | 0.23s | 0.23s | 0.30s |
| Red Hat | 0.40s | 0.40s | 0.43s |
結論:Synth 載入略慢於內建外觀,但壓力測試差距極小。**關鍵優化:減少不同圖片數量,複用圖片資源。**
| 階段 | 傳統繼承法 | Synth XML 法 |
|---|---|---|
| 圖形設計 | 2 設計師 × 2 週 = 4 人週 | 2 設計師 × 2 週 = 4 人週 |
| 程式碼轉換 | 3 程式員 × 2 月 = 6 人月 | 1 程式員 × 2 週 = 0.5 人月 |
| 總計 | 7 人月 | 6 人週 (節省 5+ 月) |
**結論**:4 人團隊(2 設計 + 2 開發)**3 週內**即可交付完整專業外觀。
Synth 以「資料驅動 UI」徹底解決 Swing 客製化難題:XML + 圖片分離關注點、設計師與開發者並行、標準化 Painter 擴展機制、效能可控。Java 1.4 的 GTK+/Windows XP 外觀即以 Synth 內建實現,證明其生產級成熟度。立即擁抱 Synth,讓你的 Swing 應用擁有媲美原生應用的視覺體驗!