技术开发 频道

高级 Synth

  图 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 的值相同。

  清单 4 展示了用于装载图像的 XML 代码。注意 sourceInsets 如何确保图像只有适当的部分被拉伸。

  清单 4. 装载图像

1 <style id="textfield">
2    <opaque value="true"/>
3    <state>
4       <font name="Aharoni" size="14"/>
5       <color value="#D2DFF2" type="BACKGROUND"/>
6       <color value="#000000" type="TEXT_FOREGROUND"/>
7    </state>
8    <imagePainter method="textFieldBorder" path="images/textfield.png"
9       sourceInsets="4 6 4 6" paintCenter="false"/>
10    <insets top="4" left="6" bottom="4" right="6"/>
11 </style>
12 <bind style="textfield" type="region" key="TextField"/>

  处理不同的状态

  从前面的例子可以看到, 标签是定义一个组件的焦点所在。在清单 3 和清单 4 中,color 和 font 标签都处在 标签内。现在我将解释 标签的作用。

  默认状态是在 标签中没有指定属性,这对于定义文本域和 label 中的颜色和字体已经足够了,因为这两种组件的状态不会改变。但是在那些状态会改变的组件中(例如按钮),可以为每种状态定义完全不同的外观。每种状态可以有它自己的颜色、字体和图像。您可以比较登录屏幕中 Cancel 按钮在默认状态(图 4)和 mouse-over 状态(图 5)下的不同。

  图 4. DEFAULT 状态下的 Cancel 按钮

  图 5. MOUSE_OVER 状态下的 Cancel 按钮

   标签只需要一个 value 属性,该属性定义了实际的组件状态。如果没有指定 value,如清单 3 和 4 所示,那么每种状态都使用默认值。如果指定 value 属性,那么可以选择 ENABLED、MOUSE_OVER、PRESSED、DISABLED、FOCUSED、SELECTED 和 DEFAULT。这些选择包含 Swing 中任何组件所有可能的状态。您还可以在不同选择间添加 and 来组合各种状态。例如,如果您想在鼠标位于按钮之上以及按钮被按下的时候改变按钮上的字体,那么可以使用状态值 MOUSE_OVER and PRESSED。

  清单 5 展示了用于处理 demo 应用程序状态的 XML。注意每种状态是如何定义不同的图像和文本颜色的。

  清单 5. 处理状态

1 <style id="button">
2    <state>
3       <imagePainter method="buttonBackground" path="images/button.png"
4          sourceInsets="9 10 9 12" paintCenter="true" stretch="true"/>
5       <insets top="9" left="10" bottom="9" right="12"/>
6       <font name="Aharoni" size="16"/>
7       <color type="TEXT_FOREGROUND" value="#FFFFFF"/>
8    </state>
9    <state value="MOUSE_OVER">
10       <imagePainter method="buttonBackground" path="images/button_on.png"
11          sourceInsets="9 10 9 12" paintCenter="true" stretch="true"/>
12       <insets top="9" left="10" bottom="9" right="12"/>
13       <color type="TEXT_FOREGROUND" value="#FFFFFF"/>
14    </state>
15    <state value="PRESSED">
16       <imagePainter method="buttonBackground" path="images/button_press.png"
17          sourceInsets="10 12 8 9" paintCenter="true" stretch="true"/>
18       <insets top="10" left="12" bottom="8" right="9"/>
19       <color type="TEXT_FOREGROUND" value="#FFFFFF"/>
20    </state>
21    <property key="Button.margin" type="insets" value="0 0 0 0"/>
22 </style>
23 <bind style="button" type="region" key="Button"/>

  处理 标签的一个重要方面是知道哪些组件有哪些状态。显然,在这个例子中,按钮可以拥有默认状态、鼠标悬停(mouse-over)状态和被按下(pressed) 状态。对于这个例子,还可以定义一个聚焦(focused)和禁用(disabled)状态。但是对于一个面板,选中(selected)状态根本不适用,当鼠标处于面板之上时如果改变面板的状态,那么只能招来抱怨。

0