最新的Swing外觀,定製UI不在話下

本文將深入透視 Synth 外觀,它是 Java 5.0 中爲 Swing 引入的最新內容。通過爲 Java UI 編程引入“皮膚”的概念,Synth 使開發人員可以爲應用程序創建和部署定製的外觀。軟件工程師 Michael Abernethy 將帶您從頭開始逐步構建一個具有 synth 外觀的應用程序,讓您充分了解 Synth 的概念。閱讀本文之後,您應該可以在短時間內創建具有專業外觀的 UI。
  
  就在 Sun 一如既往地試圖“再次引入 Java Desktop”之際,Java UI 開發人員的抱怨之詞亦已表面化:要創建完全定製的外觀實在太難。這樣做不僅要花費太多的時間,並且 Swing UI 代碼的編寫和文檔的編制也極爲不堪,常常是亂雜一氣,缺乏規劃。爲了創建完整的外觀,開發人員需要繼承 Metal 外觀的 39 個類,或者繼承 Basic 外觀的 60 個類。誰想通過重寫整個包來改變應用程序呈現外觀的方式呢?用 Swing 創建定製外觀有多難,通過下面的事實同樣可窺見一斑:在很多開發人員爲開源項目添磚加瓦的時代,Internet 上可用的自定義 Swing 外觀幾乎是鳳毛麟角 —— 總共大約是 20 個,其中少數在 SourceForge.net 上(請參閱參考資料)。
  
  美麗只是膚淺的東西
  
  進入 Synth,Sun 希望它能使應用程序外觀的個性化過程變得容易。Synth 的目標很簡單 —— 讓開發人員不必編寫任何代碼就可以創建新的外觀。這似乎是個不錯的解決方案。程序員一般沒有突出的藝術才華,而圖形設計人員通常也不是 Java 編程專家。Synth 把對外觀的所有描述從代碼中分離出來,而將其放入外部的 XML 文件和圖像文件中,爲上述問題提供了大快人心的解決之道。這種完全在外部文件中描述的外觀被稱作皮膚(skin)。
  
  Sun 的皮膚概念並不是什麼創新。例如,Winamp 有數百種皮膚,Firefox 也有幾十種皮膚,這些皮膚很容易創建,只需更改一個 XML 文件即可。想像一下,僅僅修改一個 XML 文件,就能快速、容易地爲 Java 應用程序創建一個外觀。再想想這樣一來的結果 —— 幾百個互不相同的 Swing 外觀。Java UI 開發人員當然有理由歡呼了。
  
  本文將深入分析 Synth 外觀,向您展示創建一個完整的外觀或皮膚所需知道的一切。您會看到一個帶有示例皮膚的應用程序,這個應用程序使用了 Synth 所有重要的概念。然後,我會逐步剖析這個皮膚,在構建 XML 文件的過程中,一一教會您 Synth 的各個概念。
  
  本文最後一節將盡力回答開發人員關於 Synth 性能、bug 和缺陷以及 Synth 在省時方面的表現等種種問題。閱讀本文之後,您應該會願意擁護 Synth 作爲外觀解決方案,並準備馬上使用它來創建自己的 Swing 外觀。
  
  Synth 基礎
  
  Synth 是一個白板(tabula rasa)外觀 —— 一塊完全空白的畫布,表現爲一個完全空白的面板(panel),只有在 XML 文件中定義了組件時,它纔會顯示東西。一旦定義了組件,在應用程序上設置 Synth 外觀就再容易不過了,如清單 1 所示:
  
  清單 1. 設置 Synth 外觀
  
  SynthLookAndFeel synth = new SynthLookAndFeel();
  synth.load(SynthFrame.class.getResourceAsStream("demo.xml"), SynthFrame.class);
  UIManager.setLookAndFeel(synth);
  
  但是,對於 Synth,最重要的是要理解它是 XML 代碼,而不是 Java 代碼。雖然 Synth XML 格式一開始看上去比較嚇人,但實際上很簡單。如果使用 KISS (Keep It Simple Stupid)這道符咒,您可以快速地創建一個 XML 文件,並得到一個新的、可以運行的外觀。
  
  考慮到 KISS 指令,我將首先介紹 Synth XML 文件的主要構件 —— <style> 標籤。<style> 標籤包含描述一個組件的式樣的所有信息,例如顏色、字體、圖像文件、狀態,以及一些特定於組件的屬性。雖然一個 <style> 標籤可以描述多個組件,但構建 Synth 文件的最簡便方法是爲每個 Swing 組件創建一個式樣。
  
  創建好式樣之後,便可以將式樣鏈接到一個組件。<bind> 標籤通知 Synth 引擎將一個已定義的式樣鏈接到一個組件,如清單 2 所示。這樣的組合便完全創建了組件的新外觀。
  
  清單 2. 將一種式樣鏈接到一個組件
  
  <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"/>
  
  關於 <bind> 標籤,要注意的一點是:<bind> 標籤中的 key 屬性映射到 javax.swing.plaf.synth.Region 類中的常量。Synth 引擎使用這些常量將式樣與一個實際的 Swing 組件鏈接。簡單的組件,例如 JButton 和 JTextField,使用一個常量。有些更復雜的組件,例如 JScrollBar 和 JTabbedPane,則有多個常量,用於不同的部分。
  
  我建議您在更熟悉 Synth 格式並且能夠設置 XML 中的繼承模型之前,使用每個組件一種式樣(one-style-per-component)的設置。這種結構雖然沒有利用所有 XML 的分層結構功能,但它是最容易設置、編寫代碼和調試的。
  
  在處理 Synth XML 文件時,還有一點很重要,並不是任何形式都是合法的。如果有輸入錯誤,或者在 XML 中使用了不正確的屬性,這些錯誤只有當外觀裝載期間拋出一個運行時異常時才能發現。解決方法:在將 XML 文件發佈給客戶之前,對其進行測試
  
  Demo 應用程序
  
  我將帶您構建一個簡單的登錄屏幕,用它作爲例子應用程序,向您展示 Synth XML 文件的工作原理。該屏幕提供了足夠多的組件,通過這些組件,可以看到 XML 文件的所有重要部分,如果使這些部分結合起來便可以創建一個完整的外觀。
  
  通過比較圖 1 和圖 2,具有 Ocean 外觀的登錄屏幕看上去與您預期的一樣 —— 簡單,直接,也令人厭煩。具有 Synth 外觀的登錄屏幕則完全不同。
  
  
圖 1. 具有 Ocean 外觀的 Demo 應用程序
   
  圖 2. 具有 Synth 外觀的 Demo 應用程序
   

  更改顏色和字體
  
  爲 demo 應用程序創建外觀的第一步是設置默認顏色和字體。您將把 white Aharoni 字體作爲每個組件的默認字體,如果沒有特殊設置組件的話,就使用這種字體。
  
  您可以將更改字體的 XML 放在 <style> 標籤內的任何地方。還可以將顏色嵌入到一個 <state> 標籤中。在本文的後面部分,我將更詳細地討論 <state> 標籤,但現在只需知道,一個簡單的、不帶屬性的 <state> </state> 標籤可以包含任何狀態,這個標籤正是您在這裏所需要的。
  
  color 標籤本身需要兩個屬性:
  
  value 可以是 java.awt.Color 常量的任何 String 表示(例如 RED、BLUE),或者,它可以是一種顏色的十六進制表示,前面加上 "#" (例如 #669966)。
  
  type 描述文件應該設置哪個區域的顏色。選擇有 BACKGROUND、FOREGROUND、TEXT_FOREGROUND、TEXT_BACKGROUND 和 FOCUS。
  
  font 標籤有兩個必需的屬性和一個可選屬性。這三個屬性直接映射到 java.awt.Font 類中的三個參數:
  
  name :字體的名稱(例如,Verdana、Arial)。
  
  size :字體大小,以像素爲單位。
  
  style :如果不使用這個可選標籤,那麼將得到常規外觀的字體。其他選項包括 BOLD 和 ITALIC。您還可以通過在這兩個屬性之間加一個空格來指定粗體加斜體的字體:BOLD ITALIC(這種組合屬性的技術對於 Synth XML 文件中的所有屬性都適用)。
  
  最後,通過使用 .* wildcard,將這個式樣綁定到應用程序中的每個組件,而不是將其綁定到每個 JLabel 和每個JButton。這個通配符告訴 Synth 外觀爲每個組件指定一個默認的 white Aharoni 字體。清單 3 展示了用於設置組件字體和顏色的完整 XML 代碼:
  
  清單 3. 更改多個組件的字體和顏色
  
  <style id="default">
  <font name="Aharoni" size="14"/>
  <state>
  <color value="#FFFFFF" type="FOREGROUND"/>
  </state></style><bind style="default" type="region" key=".*"/>
  
  使用圖像
  
  圖 2 中的 textfield 邊框不是常規外觀的單像素矩形邊框。可以使用一個圖像來創建這些邊框。這不是我們所熟悉的概念 —— 圖像用在 button 和 label 中已經有些時候了 —— 但您可以想像在哪些地方會出問題。如何知道光標移動到什麼地方,如何顯示文本,如何創建不同大小的文本域?這些問題可以通過圖像拉伸(image stretching)的概念來解決。一個圖像文件必須描述應用程序中文本域各個邊的長度,因此需要有一種方式來告訴 XML 文件如何適當地拉伸圖像,以及如何處理常規的 textfield 活動(carat 和文本控制)。
  
  幸運的是,從早期帶皮膚的應用程序起,就有一個方法可用於處理這種類型的拉伸。圖像必須分成 9 個區域 —— 頂部、右上、右部、右下、底部、左下、左部、左上和中間 —— 這些區域是通過 XML 文件中的一個屬性來指定的。然後呈現程序可以通過一定的方式拉伸圖像,以適合指定的空間。圖 3 展示了文本域圖像是如何拉伸的。
  
  
圖 3. 在 Synth 中圖像如何拉伸
   

  圖 3 中綠色填充區只會垂直拉伸。也就是說,當文本域比圖像高的時候,這些區域就會變高。當文本域比圖像長的時候,那些紅色填充區只會水平拉伸。而黃色填充區則是大小固定的。不管文本域的大小如何,這些區域都會如它們在圖像文件中那樣顯示。因爲這些區域不會拉伸,因此它們應該包含所有畫布、特殊底色、陰影和任何一旦拉伸就會看起來很古怪的東西。最後,中間區域是可選的。您可以選擇畫出或者忽略該區域。在我們的例子中,文本域的中間被忽略。此後,呈現程序使用這個區域來處理文本控制和 carat。也就是說,使用一個圖像文件完全畫出文本域。
  
  imagePainter 標籤提供了在外觀中使用圖像所需的所有信息。它只需要幾個屬性:
  
  path :所使用的圖像的路徑。
  
  sourceInsets :按像素計算的 insets,表示圖 3 中綠色區域的寬度和粉紅色區域的高度。它們依次映射到頂部、左部、底部和右部。
  
  method :這也許是最令人費解的屬性。它直接映射到 javax.swing.plaf.synth.SynthPainter 類中的一個函數。這個類包含大約 100 個函數,所有這些函數都以 paint 開始。每個函數映射到在一個 Swing 組件中某個特定的繪畫任務。您只需找到一個合適的函數,然後去掉 paint 字符串,並使隨後的首個字母爲小寫形式,便可以設置該屬性。例如,paintTextFieldBorder 是 textFieldBorder 的屬性。呈現程序(renderer)負責剩下的工作。
  
  paintCenter :該屬性允許您保留或者捨棄圖像的中間區域(例如在一個按鈕中)。在這個例子中,textfield 捨棄了中間區域,以便顯示文本。
  
  使用圖像畫邊框的最後一步是加大默認的 insets,以便處理用來畫這些 insets 的圖像。如果沒有更改 insets,那麼就看不見任何圖像。您需要添加一個 <insets> 標籤來增加 insets,以便在其中畫出圖像。在大多數情況下,insets 的值應該與在圖像中使用的 insets 的值相同。
  
  清單 4 展示了用於裝載圖像的 XML 代碼。注意 sourceInsets 如何確保圖像只有適當的部分被拉伸。
  
  清單 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"/>
  
  處理不同的狀態
  
  從前面的例子可以看到,<state> 標籤是定義一個組件的焦點所在。在清單 3 和清單 4 中,color 和 font 標籤都處在 <state> 標籤內。現在我將解釋 <state> 標籤的作用。
  
  默認狀態是在 <state> 標籤中沒有指定屬性,這對於定義文本域和 label 中的顏色和字體已經足夠了,因爲這兩種組件的狀態不會改變。但是在那些狀態會改變的組件中(例如按鈕),可以爲每種狀態定義完全不同的外觀。每種狀態可以有它自己的顏色、字體和圖像。您可以比較登錄屏幕中 Cancel 按鈕在默認狀態(圖 4)和 mouse-over 狀態(圖 5)下的不同。
  
  
圖 4. DEFAULT 狀態下的 Cancel 按鈕
   
  圖 5. MOUSE_OVER 狀態下的 Cancel 按鈕
   

  <state> 標籤只需要一個 value 屬性,該屬性定義了實際的組件狀態。如果沒有指定 value,如清單 3 和 4 所示,那麼每種狀態都使用默認值。如果指定 value 屬性,那麼可以選擇 ENABLED、MOUSE_OVER、PRESSED、DISABLED、FOCUSED、SELECTED 和 DEFAULT。這些選擇包含 Swing 中任何組件所有可能的狀態。您還可以在不同選擇間添加 and 來組合各種狀態。例如,如果您想在鼠標位於按鈕之上以及按鈕被按下的時候改變按鈕上的字體,那麼可以使用狀態值 MOUSE_OVER and PRESSED。
  
  清單 5 展示了用於處理 demo 應用程序狀態的 XML。注意每種狀態是如何定義不同的圖像和文本顏色的。
  
  清單 5. 處理狀態
  
  <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> 標籤的一個重要方面是知道哪些組件有哪些狀態。顯然,在這個例子中,按鈕可以擁有默認狀態、鼠標懸停(mouse-over)狀態和被按下(pressed) 狀態。對於這個例子,還可以定義一個聚焦(focused)和禁用(disabled)狀態。但是對於一個面板,選中(selected)狀態根本不適用,當鼠標處於面板之上時如果改變面板的狀態,那麼只能招來抱怨。
  
  處理特定於組件的屬性
  
  定義對每種組件都通用的 XML 屬性時,總是忽略了一些特定於組件的屬性。例如 list 的行高、單選鈕的圖標和菜單的箭頭圖標,這些都是特定於組件的屬性。可以定義的特定於組件的屬性有 100 多種,但是爲每個這樣的屬性定義一個 XML 屬性就有些過分了。因此,Synth XML 文件允許設置特定於組件的屬性。<property> 標籤就像一個 Hashtable,它定義一個鍵/值對來設置屬性。
  
  登錄屏幕示例的複選框演示瞭如何爲特定於組件的屬性編寫代碼。通過定義 imageIcon,可以設置默認狀態和選中狀態下的 CheckBox.icon。這就像是翻遍 100 個屬性找到您想要的屬性那樣簡單。
  
  清單 6 展示了爲登錄屏幕中特定於組件的屬性編寫代碼的 XML。注意要首先定義 imageIcon。然後,通過使用圖像圖標的 ID,可以爲複選框的每種狀態設置一個圖標。
  
  清單 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"/>
  
  使用定製 painter
  
  定義圖 2 中登錄屏幕例子的最後工作是用曲線繪製漸變背景。用 XML 來實現這種背景似乎有些彆扭,坦白地說,真是這樣。但這樣我便有機會展示 Synth,不限制您在 UI 設計中只使用圖像和簡單的顏色。您可以使用它來畫任何東西。
  
  Synth 允許重寫其 paint 方法(即在 javax.swing.plaf.synth.SynthPainter 類中的方法),該方法繼承自 SynthPainter,它將覆蓋那些您想要定製繪畫方式的特定函數。在這個例子中,需要定義 paintPanelBackground 方法,因爲這種設計不能以 Synth XML 格式描述。
  
  爲了使用定製的 painter,或者在 XML 中以任何方式創建一個類,可以使用 <object> 標籤。<object> 標籤允許創建和保持用於彌補 Synth 呈現程序的任何 Java 類。<object> 標籤帶有兩個元素:
  
  class :將創建的類的全名。
  
  id :用於在 XML 文檔中引用這個類實例的 ID 名。
  
  通過使用對象,不僅可以創建 BackgroundPainter 類的實例 —— 這個類將用於繪製背景,而且還可以創建 ColorUIResource 類的實例,在這個類中可以定義背景顏色。想一想:在 BackgroundPainter 類中定義背景中使用的顏色,這與 Synth 的目標是矛盾的,Synth 的目標是在一個外部 XML 文件中定義一切,而不是在一個 Java 文件中進行硬編碼。
  
  使用定製 painter 的最後一步是告訴 Synth 呈現引擎,是您自己而不是 SynthPainter 類來提供函數。在這個例子中,首先在 BackgroundPainter 類中定義 paintPanelBackground 函數,並讓 SynthPainter 類定義剩下的繪畫函數。<painter> 標籤讓您可以覆蓋 SynthPainter 函數。它帶有兩個元素:
  
  method :定製 painter 應該覆蓋的方法。從 使用圖像 一節中您已經得知,您可以在 javax.swing.plaf.synth.SynthPainter 類中找到這些函數,但是應該刪除每個函數開始部分的 paint 字符串(例如,SynthPainter 中的 paintPanelBackground 在 XML 文件中應該是 panelBackground)。
  
  id:對將覆蓋此方法的類的引用。
  
  爲了在定製 painter 中使用顏色,必須將顏色保存在 javax.swing.UIDefaults 類中。在清單 7 和清單 8 中可以看到,將顏色保存在 UIDefaults 中十分簡單,對於那些接觸過 UI 創建的人來說應該,應該比較熟悉這些內容。在 XML 文件中定義的鍵將成爲 UIManager 中的引用,在 BackgroundPainter 的 Java 代碼中,可以使用 UIManager 來獲得顏色。
  
  清單 7 展示了在例子應用程序中使用定製 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 展示了例子應用程序的定製繪畫類的 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);
  
  }}
  
  更高級的設置
  
  本節包含兩個超出登錄屏幕例子範圍的技術。在創建您自己的 Synth 外觀時,您可能發現這兩項技術很有用。
  
  繪製非 Swing 組件
  
  可以改變每個 Swing 組件的外觀這一點雖然很棒,但是還應該能夠改變其他組件 —— 開發人員創建的用於填補 Swing 空缺的組件 —— 的外觀。在這種情況下,<bind> 標籤需要作出改變,以反映正在繪製的不是一個 Swing 組件。type 屬性可以有兩種值:如果映射到一個 Swing 組件,則該值爲 region,如果映射到非 Swing 組件,則該值爲 name。因此,如果將 <bind> 標籤變爲 <bind style="mystyle" type="name" key="Custom.*"/>,則會改變每個類名以 Custom 開始的組件(例如,CustomTextField 或 CustomLabel),使它們使用 mystyle 式樣。
  
  式樣的分層結構
  
  除了在創建 XML 文件時使用 KISS 式樣之外,還可以構建分層次的一些式樣,並將這些式樣應用於組件中。清單 9 應該可以清楚地演示這一點。注意,Synth 使用最後定義的屬性來顯示組件。
  
  清單 9. 分層結構的例子
  
  <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 中的代碼使每個組件有一個黑色的背景,字體大小爲 14,但 label 組件除外,label 組件擁有紅色的背景。通過克隆 sublevel 中的 base 式樣,清單 9 複製了整個式樣。然後,您可以覆蓋所需的任何特定屬性。
  
  檢驗 Synth 的性能、可靠性和效率
  
  至此,您已經看到如何創建用於 Synth 的 XML 文件,以及如何通過更改字體、更改顏色和添加圖像來創建定製的外觀,但對於 Synth 可能還有些疑問。如果您使用 Swing 已經有一段時間,那麼我可以肯定,您首先想到的是性能問題。我設計了一些性能測試,這些測試表明,Synth 不會令您的 UI 慢如蝸牛。爲了調查您可能看到的問題(並討論我在使用 Synth 時已經碰到過的一些問題),我查看了 Java Bug Parade (請參閱 參考資料)。最後,我將回答最重要的問題 —— Synth 真的可以節省您的時間嗎?
  
  裝載那麼多圖像會不會使 Synth 變得更慢?
  
  爲了回答這個問題,我創建了兩個測試,並讓您更深切地體會 Synth 在性能方面與其他外觀的比較。第一個測試將測試示例登錄應用程序的裝載時間。該測試裝載 6 個 Synth 圖像,並將這個裝載時間與一個開發人員可能創建的一般屏幕的裝載時間進行比較。第二個測試是關於裝載時間的壓力測試 —— 一個幀中有 100 多個組件。
  
  兩個測試都將測試 Ocean 和 Motif 外觀的裝載時間,以便進行比較。爲了公正起見,我在三種機器上運行了這兩個測試 —— 一種是安裝 Windows XP 的手提電腦,一種是 SuSE Linux box,還有一種是 Red Hat Linux box。結果顯示在表 1 和表 2 中。
  
  表 1. 登錄屏幕的平均裝載時間
  
  機器配置 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 個組件的屏幕的平均裝載時間
  
  機器配置 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 外觀的裝載時間只比 Ocean 和 Motif 慢一點點。但是請注意,登錄屏幕與壓力測試會比裝載更慢一些。乍一看來,這似乎很奇怪,但如果仔細研究,便可以發現起因。壓力測試沒有裝載複選框中所使用的圖像,而登錄屏幕卻裝載了這些圖像。據此可以下結論,在 Synth 外觀中使用的每個附加圖像增加了裝載時間。與含有兩個使用兩種不同圖像的組件的應用程序相比,使用相同圖像的 100 個組件裝載起來要更快一些。減少所使用圖像的數量可以提高 Synth 裝載時間方面的性能。
  
  Synth 是不是像 Swing 一樣,在第一次發佈時滿是 bug?
  
  根據 Sun Java 開發者網站上 Bug Parade 的評判,Synth 看上去是一個比較乾淨、沒有 bug 的產品。然而,沒有哪個軟件是完美的。Synth 曾經有 125 個 bug,這與 Synth 處理 JTabbedPane 的方式不成比例。因此,如果您經歷到一些問題,不要感到驚訝。然而,根據 Sun 的辯護,這些缺陷都處於“關閉(Closed)”狀態。但通常的情況是,如果以前存在某些問題,那麼這些問題在將來也很可能會出現。
  
  雖然 bug 數據庫爲 Synth 賦予了一個相對乾淨的形象,我在處理登錄屏幕的時候還是碰到一些問題。我第一次嘗試更改 JPanel 背景顏色時遭到失敗。我創建了一個特定於 JPanel 的式樣,並將其綁定到所有 JPanel,但這樣行不通。而當我決定使用自己的定製 painter 時,事情就解決了。
  
  一個更大的問題是當狀態改變時對組件進行重新繪製。在處理按鈕及其狀態時,我發現,按鈕上的文本不能正確地改變顏色。當初始化時,作爲默認顏色的白色沒有如期顯示,並且直到觸發了狀態變化之後纔出現,然後就被重新設置爲默認顏色。如果仔細研究關於 Synth 的文檔,就可以發現這個小花絮:“雖然可以爲每種狀態提供不同的字體,但在一般情況下,當組件的狀態變化時,組件不會重新生效,所以,如果您試圖爲不同狀態使用有明顯不同大小的字體時,有可能會遇到字體大小的問題”。聽起來似乎它們遇上了試圖讓 Synth 使用老的 Swing 代碼的問題。因此,如果要在狀態改變時更改字體,那麼要小心。
  
  Synth 看上去的確很少有 bug。但如果隨處出點小問題,那些本應該行得通的代碼就會行不通,我不會對此感到驚訝。不過,變通的辦法不難找到。對於在工作中碰到的每個問題,我總能找到一個變通的辦法。
  
  利用 Synth 可以創建出完全專業的外觀嗎?
  
  回答是肯定的。Java 1.4 中發佈的 GTK+ 和 Windows XP 外觀就完全是用 Synth 創建的。(那時它不是一個已公佈的 API。) 所以這方面顯然沒有問題。
  
  用 Synth 創建一個完整的外觀比用 Java 代碼編寫這樣的外觀要快多少?
  
  這很容易計算。這兩種方法各自都包含兩個步驟:
  
  創建外觀,這通常是由圖形設計人員負責的工作。
  
  將圖形界面轉化成代碼。
  
  不管是用 Java 編寫代碼還是使用 Synth,圖形界面設計這部分工作所花的時間是相同的。根據我創建定製外觀的經驗,我估計爲一個應用程序創建一個完整的外觀需要兩個圖形設計人員兩週的時間。也就是說,圖像設計工作需要 4 人一週(person-week)的人力。
  
  通常,根據我的經驗,通過類繼承的方式將圖形界面翻譯成立即可用的外觀需要三個 Java 編程人員花大約兩個月的時間。也就是說,編寫 Java 代碼需要 6 個人一個月(person-month)的人力。加上圖形界面設計工作,通過重寫 UI 類,用 Swing 創建一個完全定製的外觀總共需要 7 個人一個月的工作量。這些數據有助於您明白爲什麼 Internet 上可供下載的定製外觀是那麼少。
  
  通過將圖形界面轉換成一個 XML 文件,Synth 可以節省大量的時間。通過 Java 編程創建外觀需要 6 個人一個月的工作量,而一個開發人員將圖形界面轉換成 Synth XML 文件只需兩個星期。用 Synth 創建完整外觀所需的工作量減少到僅僅 6 個人一週的工作量 —— 通過使用 Synth 節省了超過 5 個月的時間。對於一個由兩個圖形設計師和兩個程序員組成的團隊,在短短三個星期內便可以創建出一個完整的 Synth 外觀。
  
  結束語
  
  Synth 將皮膚的概念引入到 Swing 中。相對於傳統的用 Java 代碼編寫定製外觀的方法,Synth 最大的優勢是節省時間。一個完整的 Swing 外觀可以在不到一個月的時間裏完成,這比用 Java 語言編程的方法要快 5 倍。對於有幹勁的開發人員,在用 Java 代碼編寫一個外觀的時間裏,他可以創建 5 個 Synth 外觀。
  
  然而,Synth 並非毫無瑕疵。通過編寫 Java 代碼覆蓋 Swing 外觀,可以同時改變應用程序的外觀和感覺 。而 Synth 只允許改變應用程序的外觀。這是一個很大的不同之處。外觀是指應用程序中使用的顏色、字體和圖形。另一方面,感覺則對應於應用程序在交互期間展現出來的行爲 —— 這裏指單擊一下鼠標右鍵,那裏按下一個鍵。例如,如果您想改變一個 JList 的行爲,希望通過單擊鼠標左鍵選中條目,然後再通過單擊鼠標右鍵來刪除條目,那麼用 Synth 是無法做到這些的。您需要爲新的外觀編寫 Java 代碼。Synth 實際上應該稱爲一種新的 Swing 外觀,而不是一種普通外觀。通過 Synth 可以快速改變 UI 的外觀,但 UI 的感覺永遠都是默認的 Swing 感覺。
  
  當然,如果您想通過爲應用程序提供新的外觀來使之整潔漂亮,或者渴望看到比令人討厭的 Metal 外觀(謝天謝地,在 Java 5.0 中它已成爲歷史)更好的 Swing 應用程序外觀,那麼 Synth 是很好的一個選擇。它不存在性能問題,並且看上去 bug 也很少。Sun 已經表示,通過發佈 GTK+ 外觀,用 Synth 可以創建完整的外觀。
  
  令人吃驚的是,Synth 文檔和實例現在還很少。閱讀本文之後,對於 Synth 的工作原理您應該有一個更深的理解,並且能夠使用一個組件一個樣式標籤(one-style-tag-per-one-component)的設計來生成一個完整的 Synth XML 文檔。Synth 的繼承和分層模型爲創建 style 標籤提供了更強大的方法,但沒有它們仍然可以創建完整的外觀。理想情況是:隨着對 Synth 認識的加深,Swing UI 社區將出現皮膚數量的大爆炸。有了數百個可供選擇的外觀,通常那些加在 Swing 應用程序身上的“長相恐怖”、“醜陋”之類的責罵之詞也將永遠消失。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章