就在 Sun 一如既往地試圖“再次引入 Java Desktop”之際,Java UI 開發(fā)人員的抱怨之詞亦已表面化:要?jiǎng)?chuàng)建完全定制的外觀實(shí)在太難。這樣做不僅要花費(fèi)太多的時(shí)間,并且 Swing UI 代碼的編寫和文檔的編制也極為不堪,常常是亂雜一氣,缺乏規(guī)劃。為了創(chuàng)建完整的外觀,開發(fā)人員需要繼承 Metal 外觀的 39 個(gè)類,或者繼承 Basic 外觀的 60 個(gè)類。誰(shuí)想通過(guò)重寫整個(gè)包來(lái)改變應(yīng)用程序呈現(xiàn)外觀的方式呢?用 Swing 創(chuàng)建定制外觀有多難,通過(guò)下面的事實(shí)同樣可窺見一斑:在很多開發(fā)人員為開源項(xiàng)目添磚加瓦的時(shí)代,Internet 上可用的自定義 Swing 外觀幾乎是鳳毛麟角 —— 總共大約是 20 個(gè),其中少數(shù)在 SourceForge.net 上(請(qǐng)參閱 參考資料)。
美麗只是膚淺的東西
進(jìn)入 Synth,Sun 希望它能使應(yīng)用程序外觀的個(gè)性化過(guò)程變得容易。Synth 的目標(biāo)很簡(jiǎn)單 —— 讓開發(fā)人員不必編寫任何代碼就可以創(chuàng)建新的外觀。這似乎是個(gè)不錯(cuò)的解決方案。程序員一般沒有突出的藝術(shù)才華,而圖形設(shè)計(jì)人員通常也不是 Java 編程專家。Synth 把對(duì)外觀的所有描述從代碼中分離出來(lái),而將其放入外部的 XML 文件和圖像文件中,為上述問(wèn)題提供了大快人心的解決之道。這種完全在外部文件中描述的外觀被稱作 皮膚(skin)。
Sun 的皮膚概念并不是什么創(chuàng)新。例如,Winamp 有數(shù)百種皮膚,F(xiàn)irefox 也有幾十種皮膚,這些皮膚很容易創(chuàng)建,只需更改一個(gè) XML 文件即可。想像一下,僅僅修改一個(gè) XML 文件,就能快速、容易地為 Java 應(yīng)用程序創(chuàng)建一個(gè)外觀。再想想這樣一來(lái)的結(jié)果 —— 幾百個(gè)互不相同的 Swing 外觀。Java UI 開發(fā)人員當(dāng)然有理由歡呼了。
本文將深入分析 Synth 外觀,向您展示創(chuàng)建一個(gè)完整的外觀或皮膚所需知道的一切。您會(huì)看到一個(gè)帶有示例皮膚的應(yīng)用程序,這個(gè)應(yīng)用程序使用了 Synth 所有重要的概念。然后,我會(huì)逐步剖析這個(gè)皮膚,在構(gòu)建 XML 文件的過(guò)程中,一一教會(huì)您 Synth 的各個(gè)概念。
本文最后一節(jié)將盡力回答開發(fā)人員關(guān)于 Synth 性能、bug 和缺陷以及 Synth 在省時(shí)方面的表現(xiàn)等種種問(wèn)題。閱讀本文之后,您應(yīng)該會(huì)愿意擁護(hù) Synth 作為外觀解決方案,并準(zhǔn)備馬上使用它來(lái)創(chuàng)建自己的 Swing 外觀。
回頁(yè)首
Synth 基礎(chǔ)
Synth 是一個(gè) 白板(tabula rasa)外觀 —— 一塊完全空白的畫布,表現(xiàn)為一個(gè)完全空白的面板(panel),只有在 XML 文件中定義了組件時(shí),它才會(huì)顯示東西。一旦定義了組件,在應(yīng)用程序上設(shè)置 Synth 外觀就再容易不過(guò)了,如清單 1 所示:
清單 1. 設(shè)置 Synth 外觀
SynthLookAndFeel synth = new SynthLookAndFeel();
synth.load(SynthFrame.class.getResourceAsStream("demo.xml"), SynthFrame.class);
UIManager.setLookAndFeel(synth);
|
但是,對(duì)于 Synth,最重要的是要理解它是 XML 代碼,而不是 Java 代碼。雖然 Synth XML 格式一開始看上去比較嚇人,但實(shí)際上很簡(jiǎn)單。如果使用 KISS (Keep It Simple Stupid)這道符咒,您可以快速地創(chuàng)建一個(gè) XML 文件,并得到一個(gè)新的、可以運(yùn)行的外觀。
考慮到 KISS 指令,我將首先介紹 Synth XML 文件的主要構(gòu)件 —— <style>標(biāo)簽。<style>標(biāo)簽包含描述一個(gè)組件的式樣的所有信息,例如顏色、字體、圖像文件、狀態(tài),以及一些特定于組件的屬性。雖然一個(gè) <style>標(biāo)簽可以描述多個(gè)組件,但構(gòu)建 Synth 文件的最簡(jiǎn)便方法是為每個(gè) Swing 組件創(chuàng)建一個(gè)式樣。
創(chuàng)建好式樣之后,便可以將式樣鏈接到一個(gè)組件。<bind>標(biāo)簽通知 Synth 引擎將一個(gè)已定義的式樣鏈接到一個(gè)組件,如清單 2 所示。這樣的組合便完全創(chuàng)建了組件的新外觀。
清單 2. 將一種式樣鏈接到一個(gè)組件
<style id="textfield">
// describe colors, fonts, and states
</style>
<bind style="textfield" type="region" key="Textfield"/>
<style id="button">
// describe colors, fonts, and states
</style>
<bind style="button" type="region" key="Button"/>
|
關(guān)于 <bind>標(biāo)簽,要注意的一點(diǎn)是:<bind>標(biāo)簽中的 key屬性映射到 javax.swing.plaf.synth.Region類中的常量。Synth 引擎使用這些常量將式樣與一個(gè)實(shí)際的 Swing 組件鏈接。簡(jiǎn)單的組件,例如 JButton和 JTextField,使用一個(gè)常量。有些更復(fù)雜的組件,例如 JScrollBar和 JTabbedPane,則有多個(gè)常量,用于不同的部分。
我建議您在更熟悉 Synth 格式并且能夠設(shè)置 XML 中的繼承模型之前,使用每個(gè)組件一種式樣(one-style-per-component)的設(shè)置。這種結(jié)構(gòu)雖然沒有利用所有 XML 的分層結(jié)構(gòu)功能,但它是最容易設(shè)置、編寫代碼和調(diào)試的。
在處理 Synth XML 文件時(shí),還有一點(diǎn)很重要,并不是任何形式都是合法的。如果有輸入錯(cuò)誤,或者在 XML 中使用了不正確的屬性,這些錯(cuò)誤只有當(dāng)外觀裝載期間拋出一個(gè)運(yùn)行時(shí)異常時(shí)才能發(fā)現(xiàn)。解決方法:在將 XML 文件發(fā)布給客戶之前,對(duì)其進(jìn)行測(cè)試。
回頁(yè)首
Demo 應(yīng)用程序
我將帶您構(gòu)建一個(gè)簡(jiǎn)單的登錄屏幕,用它作為例子應(yīng)用程序,向您展示 Synth XML 文件的工作原理。該屏幕提供了足夠多的組件,通過(guò)這些組件,可以看到 XML 文件的所有重要部分,如果使這些部分結(jié)合起來(lái)便可以創(chuàng)建一個(gè)完整的外觀。
通過(guò)比較圖 1 和圖 2,具有 Ocean 外觀的登錄屏幕看上去與您預(yù)期的一樣 —— 簡(jiǎn)單,直接,也令人厭煩。具有 Synth 外觀的登錄屏幕則完全不同。
圖 1. 具有 Ocean 外觀的 Demo 應(yīng)用程序
圖 2. 具有 Synth 外觀的 Demo 應(yīng)用程序
回頁(yè)首
更改顏色和字體
為 demo 應(yīng)用程序創(chuàng)建外觀的第一步是設(shè)置默認(rèn)顏色和字體。您將把 white Aharoni 字體作為每個(gè)組件的默認(rèn)字體,如果沒有特殊設(shè)置組件的話,就使用這種字體。
您可以將更改字體的 XML 放在 <style>標(biāo)簽內(nèi)的任何地方。還可以將顏色嵌入到一個(gè) <state>標(biāo)簽中。在本文的后面部分,我將更詳細(xì)地討論 <state>標(biāo)簽,但現(xiàn)在只需知道,一個(gè)簡(jiǎn)單的、不帶屬性的 <state> </state>標(biāo)簽可以包含任何狀態(tài),這個(gè)標(biāo)簽正是您在這里所需要的。
color標(biāo)簽本身需要兩個(gè)屬性:
value 可以是 java.awt.Color常量的任何 String表示(例如 RED、BLUE),或者,它可以是一種顏色的十六進(jìn)制表示,前面加上 "#" (例如 #669966)。
type 描述文件應(yīng)該設(shè)置哪個(gè)區(qū)域的顏色。選擇有BACKGROUND、FOREGROUND、TEXT_FOREGROUND、TEXT_BACKGROUND和 FOCUS。
font標(biāo)簽有兩個(gè)必需的屬性和一個(gè)可選屬性。這三個(gè)屬性直接映射到 java.awt.Font類中的三個(gè)參數(shù):
name :字體的名稱(例如,Verdana、Arial)。
size :字體大小,以像素為單位。
style :如果不使用這個(gè)可選標(biāo)簽,那么將得到常規(guī)外觀的字體。其他選項(xiàng)包括 BOLD和 ITALIC。您還可以通過(guò)在這兩個(gè)屬性之間加一個(gè)空格來(lái)指定粗體加斜體的字體:BOLD ITALIC(這種組合屬性的技術(shù)對(duì)于 Synth XML 文件中的所有屬性都適用)。
最后,通過(guò)使用 .* wildcard,將這個(gè)式樣綁定到應(yīng)用程序中的每個(gè)組件,而不是將其綁定到每個(gè) JLabel和每個(gè) JButton。這個(gè)通配符告訴 Synth 外觀為每個(gè)組件指定一個(gè)默認(rèn)的 white Aharoni 字體。清單 3 展示了用于設(shè)置組件字體和顏色的完整 XML 代碼:
清單 3. 更改多個(gè)組件的字體和顏色
<style id="default">
<font name="Aharoni" size="14"/>
<state>
<color value="#FFFFFF" type="FOREGROUND"/>
</state>
</style>
<bind style="default" type="region" key=".*"/>
|
回頁(yè)首
使用圖像
圖 2中的 textfield邊框不是常規(guī)外觀的單像素矩形邊框。可以使用一個(gè)圖像來(lái)創(chuàng)建這些邊框。這不是我們所熟悉的概念 —— 圖像用在 button 和 label 中已經(jīng)有些時(shí)候了 —— 但您可以想像在哪些地方會(huì)出問(wèn)題。如何知道光標(biāo)移動(dòng)到什么地方,如何顯示文本,如何創(chuàng)建不同大小的文本域?這些問(wèn)題可以通過(guò) 圖像拉伸(image stretching)的概念來(lái)解決。一個(gè)圖像文件必須描述應(yīng)用程序中文本域各個(gè)邊的長(zhǎng)度,因此需要有一種方式來(lái)告訴 XML 文件如何適當(dāng)?shù)乩靾D像,以及如何處理常規(guī)的 textfield活動(dòng)(carat 和文本控制)。
幸運(yùn)的是,從早期帶皮膚的應(yīng)用程序起,就有一個(gè)方法可用于處理這種類型的拉伸。圖像必須分成 9 個(gè)區(qū)域 —— 頂部、右上、右部、右下、底部、左下、左部、左上和中間 —— 這些區(qū)域是通過(guò) XML 文件中的一個(gè)屬性來(lái)指定的。然后呈現(xiàn)程序可以通過(guò)一定的方式拉伸圖像,以適合指定的空間。圖 3 展示了文本域圖像是如何拉伸的。
圖 3. 在 Synth 中圖像如何拉伸
圖 3 中綠色填充區(qū)只會(huì)垂直拉伸。也就是說(shuō),當(dāng)文本域比圖像高的時(shí)候,這些區(qū)域就會(huì)變高。當(dāng)文本域比圖像長(zhǎng)的時(shí)候,那些紅色填充區(qū)只會(huì)水平拉伸。而黃色填充區(qū)則是大小固定的。不管文本域的大小如何,這些區(qū)域都會(huì)如它們?cè)趫D像文件中那樣顯示。因?yàn)檫@些區(qū)域不會(huì)拉伸,因此它們應(yīng)該包含所有畫布、特殊底色、陰影和任何一旦拉伸就會(huì)看起來(lái)很古怪的東西。最后,中間區(qū)域是可選的。您可以選擇畫出或者忽略該區(qū)域。在我們的例子中,文本域的中間被忽略。此后,呈現(xiàn)程序使用這個(gè)區(qū)域來(lái)處理文本控制和 carat。也就是說(shuō),使用一個(gè)圖像文件完全畫出文本域。
imagePainter標(biāo)簽提供了在外觀中使用圖像所需的所有信息。它只需要幾個(gè)屬性:
path :所使用的圖像的路徑。
sourceInsets :按像素計(jì)算的 insets,表示圖 3 中綠色區(qū)域的寬度和粉紅色區(qū)域的高度。它們依次映射到頂部、左部、底部和右部。
method :這也許是最令人費(fèi)解的屬性。它直接映射到 javax.swing.plaf.synth.SynthPainter類中的一個(gè)函數(shù)。這個(gè)類包含大約 100 個(gè)函數(shù),所有這些函數(shù)都以 paint開始。每個(gè)函數(shù)映射到在一個(gè) Swing 組件中某個(gè)特定的繪畫任務(wù)。您只需找到一個(gè)合適的函數(shù),然后去掉 paint字符串,并使隨后的首個(gè)字母為小寫形式,便可以設(shè)置該屬性。例如,paintTextFieldBorder是 textFieldBorder的屬性。呈現(xiàn)程序(renderer)負(fù)責(zé)剩下的工作。
paintCenter :該屬性允許您保留或者舍棄圖像的中間區(qū)域(例如在一個(gè)按鈕中)。在這個(gè)例子中,textfield舍棄了中間區(qū)域,以便顯示文本。
使用圖像畫邊框的最后一步是加大默認(rèn)的 insets,以便處理用來(lái)畫這些 insets 的圖像。如果沒有更改 insets,那么就看不見任何圖像。您需要添加一個(gè) <insets>標(biāo)簽來(lái)增加 insets,以便在其中畫出圖像。在大多數(shù)情況下,insets 的值應(yīng)該與在圖像中使用的 insets 的值相同。
清單 4 展示了用于裝載圖像的 XML 代碼。注意 sourceInsets如何確保圖像只有適當(dāng)?shù)牟糠直焕臁?/p>
清單 4. 裝載圖像
<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"/>
|
回頁(yè)首
處理不同的狀態(tài)
從前面的例子可以看到,<state>標(biāo)簽是定義一個(gè)組件的焦點(diǎn)所在。在清單 3和清單 4中,color 和 font 標(biāo)簽都處在 <state>標(biāo)簽內(nèi)。現(xiàn)在我將解釋 <state>標(biāo)簽的作用。
默認(rèn)狀態(tài)是在 <state>標(biāo)簽中沒有指定屬性,這對(duì)于定義文本域和 label 中的顏色和字體已經(jīng)足夠了,因?yàn)檫@兩種組件的狀態(tài)不會(huì)改變。但是在那些狀態(tài)會(huì)改變的組件中(例如按鈕),可以為每種狀態(tài)定義完全不同的外觀。每種狀態(tài)可以有它自己的顏色、字體和圖像。您可以比較登錄屏幕中 Cancel按鈕在默認(rèn)狀態(tài)(圖 4)和 mouse-over 狀態(tài)(圖 5)下的不同。
圖 4. DEFAULT 狀態(tài)下的 Cancel 按鈕
圖 5. MOUSE_OVER 狀態(tài)下的 Cancel 按鈕
<state>標(biāo)簽只需要一個(gè) value屬性,該屬性定義了實(shí)際的組件狀態(tài)。如果沒有指定 value,如清單 3 和 4 所示,那么每種狀態(tài)都使用默認(rèn)值。如果指定 value屬性,那么可以選擇ENABLED、MOUSE_OVER、PRESSED、DISABLED、FOCUSED、SELECTED和 DEFAULT。這些選擇包含 Swing 中任何組件所有可能的狀態(tài)。您還可以在不同選擇間添加 and來(lái)組合各種狀態(tài)。例如,如果您想在鼠標(biāo)位于按鈕之上以及按鈕被按下的時(shí)候改變按鈕上的字體,那么可以使用狀態(tài)值 MOUSE_OVER and PRESSED。
清單 5 展示了用于處理 demo 應(yīng)用程序狀態(tài)的 XML。注意每種狀態(tài)是如何定義不同的圖像和文本顏色的。
清單 5. 處理狀態(tài)
<style id="button">
<state>
<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"
sourceInsets="9 10 9 12" paintCenter="true" stretch="true"/>
<insets top="9" left="10" bottom="9" right="12"/>
<color type="TEXT_FOREGROUND" value="#FFFFFF"/>
</state>
<state value="PRESSED">
<imagePainter method="buttonBackground" path="images/button_press.png"
sourceInsets="10 12 8 9" paintCenter="true" stretch="true"/>
<insets top="10" left="12" bottom="8" right="9"/>
<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"/>
|
處理 <state>標(biāo)簽的一個(gè)重要方面是知道哪些組件有哪些狀態(tài)。顯然,在這個(gè)例子中,按鈕可以擁有默認(rèn)狀態(tài)、鼠標(biāo)懸停(mouse-over)狀態(tài)和被按下(pressed) 狀態(tài)。對(duì)于這個(gè)例子,還可以定義一個(gè)聚焦(focused)和禁用(disabled)狀態(tài)。但是對(duì)于一個(gè)面板,選中(selected)狀態(tài)根本不適用,當(dāng)鼠標(biāo)處于面板之上時(shí)如果改變面板的狀態(tài),那么只能招來(lái)抱怨。
回頁(yè)首
處理特定于組件的屬性
定義對(duì)每種組件都通用的 XML 屬性時(shí),總是忽略了一些特定于組件的屬性。例如 list 的行高、單選鈕的圖標(biāo)和菜單的箭頭圖標(biāo),這些都是特定于組件的屬性??梢远x的特定于組件的屬性有 100 多種,但是為每個(gè)這樣的屬性定義一個(gè) XML 屬性就有些過(guò)分了。因此,Synth XML 文件允許設(shè)置特定于組件的屬性。<property>標(biāo)簽就像一個(gè) Hashtable,它定義一個(gè)鍵 / 值對(duì)來(lái)設(shè)置屬性。
登錄屏幕示例的復(fù)選框演示了如何為特定于組件的屬性編寫代碼。通過(guò)定義 imageIcon,可以設(shè)置默認(rèn)狀態(tài)和選中狀態(tài)下的CheckBox.icon。這就像是翻遍 100 個(gè)屬性找到您想要的屬性那樣簡(jiǎn)單。
清單 6 展示了為登錄屏幕中特定于組件的屬性編寫代碼的 XML。注意要首先定義 imageIcon。然后,通過(guò)使用圖像圖標(biāo)的 ID,可以為復(fù)選框的每種狀態(tài)設(shè)置一個(gè)圖標(biāo)。
清單 6. 定義特定于組件的屬性
<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"/>
|
回頁(yè)首
使用定制 painter
定義 圖 2中登錄屏幕例子的最后工作是用曲線繪制漸變背景。用 XML 來(lái)實(shí)現(xiàn)這種背景似乎有些別扭,坦白地說(shuō),真是這樣。但這樣我便有機(jī)會(huì)展示 Synth,不限制您在 UI 設(shè)計(jì)中只使用圖像和簡(jiǎn)單的顏色。您可以使用它來(lái)畫任何東西。
Synth 允許重寫其 paint 方法(即在 javax.swing.plaf.synth.SynthPainter類中的方法),該方法繼承自 SynthPainter,它將覆蓋那些您想要定制繪畫方式的特定函數(shù)。在這個(gè)例子中,需要定義 paintPanelBackground方法,因?yàn)檫@種設(shè)計(jì)不能以 Synth XML 格式描述。
為了使用定制的 painter,或者在 XML 中以任何方式創(chuàng)建一個(gè)類,可以使用 <object>標(biāo)簽。<object>標(biāo)簽允許創(chuàng)建和保持用于彌補(bǔ) Synth 呈現(xiàn)程序的任何 Java 類。<object>標(biāo)簽帶有兩個(gè)元素:
class :將創(chuàng)建的類的全名。
id :用于在 XML 文檔中引用這個(gè)類實(shí)例的 ID 名。
通過(guò)使用對(duì)象,不僅可以創(chuàng)建 BackgroundPainter類的實(shí)例 —— 這個(gè)類將用于繪制背景,而且還可以創(chuàng)建 ColorUIResource類的實(shí)例,在這個(gè)類中可以定義背景顏色。想一想:在 BackgroundPainter類中定義背景中使用的顏色,這與 Synth 的目標(biāo)是矛盾的,Synth 的目標(biāo)是在一個(gè)外部 XML 文件中定義一切,而不是在一個(gè) Java 文件中進(jìn)行硬編碼。
使用定制 painter 的最后一步是告訴 Synth 呈現(xiàn)引擎,是您自己而不是 SynthPainter類來(lái)提供函數(shù)。在這個(gè)例子中,首先在BackgroundPainter類中定義 paintPanelBackground函數(shù),并讓 SynthPainter類定義剩下的繪畫函數(shù)。<painter> 標(biāo)簽讓您可以覆蓋 SynthPainter 函數(shù)。它帶有兩個(gè)元素:
method :定制 painter 應(yīng)該覆蓋的方法。從 使用圖像一節(jié)中您已經(jīng)得知,您可以在 javax.swing.plaf.synth.SynthPainter 類中找到這些函數(shù),但是應(yīng)該刪除每個(gè)函數(shù)開始部分的 paint字符串(例如,SynthPainter中的 paintPanelBackground在 XML 文件中應(yīng)該是 panelBackground)。
- id:對(duì)將覆蓋此方法的類的引用。
為了在定制 painter 中使用顏色,必須將顏色保存在 javax.swing.UIDefaults類中。在清單 7 和清單 8 中可以看到,將顏色保存在UIDefaults中十分簡(jiǎn)單,對(duì)于那些接觸過(guò) UI 創(chuàng)建的人來(lái)說(shuō)應(yīng)該,應(yīng)該比較熟悉這些內(nèi)容。在 XML 文件中定義的鍵將成為UIManager中的引用,在 BackgroundPainter的 Java 代碼中,可以使用 UIManager來(lái)獲得顏色。
清單 7 展示了在例子應(yīng)用程序中使用定制 painter 的 XML 代碼。注意必須首先定義顏色。
清單 7. 使用定制 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"/>
|
清單 8 展示了例子應(yīng)用程序的定制繪畫類的 Java 代碼:
清單 8. 定制繪畫的 Java 代碼
public class BackgroundPainter extends SynthPainter
{
public void paintPanelBackground(SynthContext context,
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;
GradientPaint grPaint = new GradientPaint(
(float)x, (float)y, start,
(float)w, (float)h, end);
g2.setPaint(grPaint);
g2.fillRect(x, y, w, h);
g2.setPaint(null);
g2.setColor(new Color(255, 255, 255, 120));
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
CubicCurve2D.Double arc2d = new CubicCurve2D.Double(
0, h/4, w/3, h/10, 66 * w, 1.5 * h, w, h/8);
g2.draw(arc2d);
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
}
}
|
回頁(yè)首
更高級(jí)的設(shè)置
本節(jié)包含兩個(gè)超出登錄屏幕例子范圍的技術(shù)。在創(chuàng)建您自己的 Synth 外觀時(shí),您可能發(fā)現(xiàn)這兩項(xiàng)技術(shù)很有用。
繪制非 Swing 組件
可以改變每個(gè) Swing 組件的外觀這一點(diǎn)雖然很棒,但是還應(yīng)該能夠改變其他組件 —— 開發(fā)人員創(chuàng)建的用于填補(bǔ) Swing 空缺的組件 —— 的外觀。在這種情況下,<bind>標(biāo)簽需要作出改變,以反映正在繪制的不是一個(gè) Swing 組件。type屬性可以有兩種值:如果映射到一個(gè) Swing 組件,則該值為 region,如果映射到非 Swing 組件,則該值為 name。因此,如果將 <bind>標(biāo)簽變?yōu)?nbsp;<bind style="mystyle" type="name" key="Custom.*"/>,則會(huì)改變每個(gè)類名以 Custom開始的組件(例如,CustomTextField或CustomLabel),使它們使用 mystyle式樣。
式樣的分層結(jié)構(gòu)
除了在創(chuàng)建 XML 文件時(shí)使用 KISS 式樣之外,還可以構(gòu)建分層次的一些式樣,并將這些式樣應(yīng)用于組件中。清單 9 應(yīng)該可以清楚地演示這一點(diǎn)。注意,Synth 使用最后定義的屬性來(lái)顯示組件。
清單 9. 分層結(jié)構(gòu)的例子
<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"/>
|
清單 9 中的代碼使每個(gè)組件有一個(gè)黑色的背景,字體大小為 14,但 label 組件除外,label 組件擁有紅色的背景。通過(guò)克隆sublevel中的 base式樣,清單 9 復(fù)制了整個(gè)式樣。然后,您可以覆蓋所需的任何特定屬性。
回頁(yè)首
檢驗(yàn) Synth 的性能、可靠性和效率
至此,您已經(jīng)看到如何創(chuàng)建用于 Synth 的 XML 文件,以及如何通過(guò)更改字體、更改顏色和添加圖像來(lái)創(chuàng)建定制的外觀,但對(duì)于 Synth 可能還有些疑問(wèn)。如果您使用 Swing 已經(jīng)有一段時(shí)間,那么我可以肯定,您首先想到的是性能問(wèn)題。我設(shè)計(jì)了一些性能測(cè)試,這些測(cè)試表明,Synth 不會(huì)令您的 UI 慢如蝸牛。為了調(diào)查您可能看到的問(wèn)題(并討論我在使用 Synth 時(shí)已經(jīng)碰到過(guò)的一些問(wèn)題),我查看了 Java Bug Parade (請(qǐng)參閱 參考資料)。最后,我將回答最重要的問(wèn)題 —— Synth 真的可以節(jié)省您的時(shí)間嗎?
裝載那么多圖像會(huì)不會(huì)使 Synth 變得更慢?
為了回答這個(gè)問(wèn)題,我創(chuàng)建了兩個(gè)測(cè)試,并讓您更深切地體會(huì) Synth 在性能方面與其他外觀的比較。第一個(gè)測(cè)試將測(cè)試示例登錄應(yīng)用程序的裝載時(shí)間。該測(cè)試裝載 6 個(gè) Synth 圖像,并將這個(gè)裝載時(shí)間與一個(gè)開發(fā)人員可能創(chuàng)建的一般屏幕的裝載時(shí)間進(jìn)行比較。第二個(gè)測(cè)試是關(guān)于裝載時(shí)間的壓力測(cè)試 —— 一個(gè)幀中有 100 多個(gè)組件。
兩個(gè)測(cè)試都將測(cè)試 Ocean 和 Motif 外觀的裝載時(shí)間,以便進(jìn)行比較。為了公正起見,我在三種機(jī)器上運(yùn)行了這兩個(gè)測(cè)試 —— 一種是安裝 Windows XP 的手提電腦,一種是 SuSE Linux box,還有一種是 Red Hat Linux box。結(jié)果顯示在表 1 和表 2 中。
表 1. 登錄屏幕的平均裝載時(shí)間
| 機(jī)器配置 |
Ocean |
Motif |
Synth |
| Windows XP - 1.7GHz - 2GB RAM |
.32 seconds |
.29 seconds |
.57 seconds |
| SuSE Linux 9.0 - 3.3GHz - 2GB RAM |
.23 seconds |
.20 seconds |
.45 seconds |
| Red Hat Linux 3.0 - 1.4GHz - 512MB RAM |
.37 seconds |
.32 seconds |
.61 seconds |
表 2. 包含 100 個(gè)組件的屏幕的平均裝載時(shí)間
| 機(jī)器配置 |
Ocean |
Motif |
Synth |
| Windows XP - 1.7GHz - 2GB RAM |
.33 seconds |
.32 seconds |
.34 seconds |
| SuSE Linux 9.0 - 3.3GHz - 2GB RAM |
.23 seconds |
.23 seconds |
.30 seconds |
| Red Hat Linux 3.0 - 1.4GHz - 512MB RAM |
.40 seconds |
.40 seconds |
.43 seconds |
您可以看到,Synth 外觀的裝載時(shí)間只比 Ocean 和 Motif 慢一點(diǎn)點(diǎn)。但是請(qǐng)注意,登錄屏幕與壓力測(cè)試會(huì)比裝載更慢一些。乍一看來(lái),這似乎很奇怪,但如果仔細(xì)研究,便可以發(fā)現(xiàn)起因。壓力測(cè)試沒有裝載復(fù)選框中所使用的圖像,而登錄屏幕卻裝載了這些圖像。據(jù)此可以下結(jié)論,在 Synth 外觀中使用的每個(gè)附加圖像增加了裝載時(shí)間。與含有兩個(gè)使用兩種不同圖像的組件的應(yīng)用程序相比,使用相同圖像的 100 個(gè)組件裝載起來(lái)要更快一些。減少所使用圖像的數(shù)量可以提高 Synth 裝載時(shí)間方面的性能。
Synth 是不是像 Swing 一樣,在第一次發(fā)布時(shí)滿是 bug ?
根據(jù) Sun Java 開發(fā)者網(wǎng)站上 Bug Parade 的評(píng)判,Synth 看上去是一個(gè)比較干凈、沒有 bug 的產(chǎn)品。然而,沒有哪個(gè)軟件是完美的。Synth 曾經(jīng)有 125 個(gè) bug,這與 Synth 處理 JTabbedPane的方式不成比例。因此,如果您經(jīng)歷到一些問(wèn)題,不要感到驚訝。然而,根據(jù) Sun 的辯護(hù),這些缺陷都處于“關(guān)閉(Closed)”狀態(tài)。但通常的情況是,如果以前存在某些問(wèn)題,那么這些問(wèn)題在將來(lái)也很可能會(huì)出現(xiàn)。
雖然 bug 數(shù)據(jù)庫(kù)為 Synth 賦予了一個(gè)相對(duì)干凈的形象,我在處理登錄屏幕的時(shí)候還是碰到一些問(wèn)題。我第一次嘗試更改 JPanel背景顏色時(shí)遭到失敗。我創(chuàng)建了一個(gè)特定于 JPanel的式樣,并將其綁定到所有 JPanel,但這樣行不通。而當(dāng)我決定使用自己的定制 painter 時(shí),事情就解決了。
一個(gè)更大的問(wèn)題是當(dāng)狀態(tài)改變時(shí)對(duì)組件進(jìn)行重新繪制。在處理按鈕及其狀態(tài)時(shí),我發(fā)現(xiàn),按鈕上的文本不能正確地改變顏色。當(dāng)初始化時(shí),作為默認(rèn)顏色的白色沒有如期顯示,并且直到觸發(fā)了狀態(tài)變化之后才出現(xiàn),然后就被重新設(shè)置為默認(rèn)顏色。如果仔細(xì)研究關(guān)于 Synth 的文檔,就可以發(fā)現(xiàn)這個(gè)小花絮:“雖然可以為每種狀態(tài)提供不同的字體,但在一般情況下,當(dāng)組件的狀態(tài)變化時(shí),組件不會(huì)重新生效,所以,如果您試圖為不同狀態(tài)使用有明顯不同大小的字體時(shí),有可能會(huì)遇到字體大小的問(wèn)題”。聽起來(lái)似乎它們遇上了試圖讓 Synth 使用老的 Swing 代碼的問(wèn)題。因此,如果要在狀態(tài)改變時(shí)更改字體,那么要小心。
Synth 看上去的確很少有 bug。但如果隨處出點(diǎn)小問(wèn)題,那些本應(yīng)該行得通的代碼就會(huì)行不通,我不會(huì)對(duì)此感到驚訝。不過(guò),變通的辦法不難找到。對(duì)于在工作中碰到的每個(gè)問(wèn)題,我總能找到一個(gè)變通的辦法。
利用 Synth 可以創(chuàng)建出完全專業(yè)的外觀嗎?
回答是肯定的。Java 1.4 中發(fā)布的 GTK+ 和 Windows XP 外觀就完全是用 Synth 創(chuàng)建的。(那時(shí)它不是一個(gè)已公布的 API。) 所以這方面顯然沒有問(wèn)題。
用 Synth 創(chuàng)建一個(gè)完整的外觀比用 Java 代碼編寫這樣的外觀要快多少?
這很容易計(jì)算。這兩種方法各自都包含兩個(gè)步驟:
- 創(chuàng)建外觀,這通常是由圖形設(shè)計(jì)人員負(fù)責(zé)的工作。
- 將圖形界面轉(zhuǎn)化成代碼。
不管是用 Java 編寫代碼還是使用 Synth,圖形界面設(shè)計(jì)這部分工作所花的時(shí)間是相同的。根據(jù)我創(chuàng)建定制外觀的經(jīng)驗(yàn),我估計(jì)為一個(gè)應(yīng)用程序創(chuàng)建一個(gè)完整的外觀需要兩個(gè)圖形設(shè)計(jì)人員兩周的時(shí)間。也就是說(shuō),圖像設(shè)計(jì)工作需要 4 人一周(person-week)的人力。
通常,根據(jù)我的經(jīng)驗(yàn),通過(guò)類繼承的方式將圖形界面翻譯成立即可用的外觀需要三個(gè) Java 編程人員花大約兩個(gè)月的時(shí)間。也就是說(shuō),編寫 Java 代碼需要 6 個(gè)人一個(gè)月(person-month)的人力。加上圖形界面設(shè)計(jì)工作,通過(guò)重寫 UI 類,用 Swing 創(chuàng)建一個(gè)完全定制的外觀總共需要 7 個(gè)人一個(gè)月的工作量。這些數(shù)據(jù)有助于您明白為什么 Internet 上可供下載的定制外觀是那么少。
通過(guò)將圖形界面轉(zhuǎn)換成一個(gè) XML 文件,Synth 可以節(jié)省大量的時(shí)間。通過(guò) Java 編程創(chuàng)建外觀需要 6 個(gè)人一個(gè)月的工作量,而一個(gè)開發(fā)人員將圖形界面轉(zhuǎn)換成 Synth XML 文件只需兩個(gè)星期。用 Synth 創(chuàng)建完整外觀所需的工作量減少到僅僅 6 個(gè)人一周的工作量 —— 通過(guò)使用 Synth 節(jié)省了超過(guò) 5 個(gè)月的時(shí)間。對(duì)于一個(gè)由兩個(gè)圖形設(shè)計(jì)師和兩個(gè)程序員組成的團(tuán)隊(duì),在短短三個(gè)星期內(nèi)便可以創(chuàng)建出一個(gè)完整的 Synth 外觀。
回頁(yè)首
結(jié)束語(yǔ)
Synth 將皮膚的概念引入到 Swing 中。相對(duì)于傳統(tǒng)的用 Java 代碼編寫定制外觀的方法,Synth 最大的優(yōu)勢(shì)是節(jié)省時(shí)間。一個(gè)完整的 Swing 外觀可以在不到一個(gè)月的時(shí)間里完成,這比用 Java 語(yǔ)言編程的方法要快 5 倍。對(duì)于有干勁的開發(fā)人員,在用 Java 代碼編寫一個(gè)外觀的時(shí)間里,他可以創(chuàng)建 5 個(gè) Synth 外觀。
然而,Synth 并非毫無(wú)瑕疵。通過(guò)編寫 Java 代碼覆蓋 Swing 外觀,可以同時(shí)改變應(yīng)用程序的外觀和 感覺。而 Synth 只允許改變應(yīng)用程序的外觀。這是一個(gè)很大的不同之處。外觀是指應(yīng)用程序中使用的顏色、字體和圖形。另一方面,感覺則對(duì)應(yīng)于應(yīng)用程序在交互期間展現(xiàn)出來(lái)的行為 —— 這里指單擊一下鼠標(biāo)右鍵,那里按下一個(gè)鍵。例如,如果您想改變一個(gè) JList的行為,希望通過(guò)單擊鼠標(biāo)左鍵選中條目,然后再通過(guò)單擊鼠標(biāo)右鍵來(lái)刪除條目,那么用 Synth 是無(wú)法做到這些的。您需要為新的外觀編寫 Java 代碼。Synth 實(shí)際上應(yīng)該稱為一種新的 Swing 外觀,而不是一種普通外觀。通過(guò) Synth 可以快速改變 UI 的外觀,但 UI 的感覺永遠(yuǎn)都是默認(rèn)的 Swing 感覺。
當(dāng)然,如果您想通過(guò)為應(yīng)用程序提供新的外觀來(lái)使之整潔漂亮,或者渴望看到比令人討厭的 Metal 外觀(謝天謝地,在 Java 5.0 中它已成為歷史)更好的 Swing 應(yīng)用程序外觀,那么 Synth 是很好的一個(gè)選擇。它不存在性能問(wèn)題,并且看上去 bug 也很少。Sun 已經(jīng)表示,通過(guò)發(fā)布 GTK+ 外觀,用 Synth 可以創(chuàng)建完整的外觀。
令人吃驚的是,Synth 文檔和實(shí)例現(xiàn)在還很少。閱讀本文之后,對(duì)于 Synth 的工作原理您應(yīng)該有一個(gè)更深的理解,并且能夠使用一個(gè)組件一個(gè)樣式標(biāo)簽(one-style-tag-per-one-component)的設(shè)計(jì)來(lái)生成一個(gè)完整的 Synth XML 文檔。Synth 的繼承和分層模型為創(chuàng)建 style 標(biāo)簽提供了更強(qiáng)大的方法,但沒有它們?nèi)匀豢梢詣?chuàng)建完整的外觀。理想情況是:隨著對(duì) Synth 認(rèn)識(shí)的加深,Swing UI 社區(qū)將出現(xiàn)皮膚數(shù)量的大爆炸。有了數(shù)百個(gè)可供選擇的外觀,通常那些加在 Swing 應(yīng)用程序身上的“長(zhǎng)相恐怖”、“丑陋”之類的責(zé)罵之詞也將永遠(yuǎn)消失。
回頁(yè)首
下載
| 名字 | 大小 | 下載方法 |
| synth.jar |
21 KB |
HTTP |
關(guān)于下載方法的信息
參考資料
關(guān)于作者
Michael Abernethy 目前是 WebSphere 系統(tǒng)管理功能性測(cè)試團(tuán)隊(duì)的負(fù)責(zé)人。之前,他在 WebSphere Services 團(tuán)隊(duì)工作了 4 年,一直在 WebSphere 上編寫、部署應(yīng)用程序。在業(yè)余時(shí)間里,他還參與了 Swing 和 UI 開發(fā)。您可以通過(guò) Michael 的郵箱 mabernet@us.ibm.com 與他聯(lián)系。
為本文評(píng)分
平均分 (10個(gè)評(píng)分)
評(píng)論
回頁(yè)首