最新的Swing外观,定制UI不在话下
原始位置:http://java.chinaitlab.com/Swing/38394.html
本文将深入透视 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 外观的登录屏幕则完全不同。

图 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 中绿色填充区只会垂直拉伸。也就是说,当文本域比图像高的时候,这些区域就会变高。当文本域比图像长的时候,那些红色填充区只会水平拉伸。而黄色填充区则是大小固定的。不管文本域的大小如何,这些区域都会如它们在图像文件中那样显示。因为这些区域不会拉伸,因此它们应该包含所有画布、特殊底色、阴影和任何一旦拉伸就会看起来很古怪的东西。最后,中间区域是可选的。您可以选择画出或者忽略该区域。在我们的例子中,文本域的中间被忽略。此后,呈现程序使用这个区域来处理文本控制和 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"/>
推荐文章 |
