合成的外观和感觉

原文: https://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/synth.html

创建自定义外观或修改现有外观可能是一项艰巨的任务。 javax.swing.plaf.synth 软件包可用于创建自定义外观,而且工作量更少。您可以以编程方式或通过使用外部 XML 文件来创建 Synth 外观。下面的讨论专门用于使用外部 XML 文件创建 Synth 外观。 API 程序文档中讨论了以编程方式创建 Synth c。

使用 Synth 外观,您可以提供“外观”。 Synth 本身就提供了“感觉”。因此,您可以将 Synth L& F 视为“皮肤”。

合成架构

每个组件的

Synth 的运行级别比组件更精细 - 这个粒度级别称为“区域”。每个组件都有一个或多个区域。许多组件只有一个区域,例如JButton。其他有多个区域,例如JScrollBar。 Synth 提供的每个ComponentUIsSynthStyleComponentUI定义的每个区域相关联。例如,Synth 为JScrollBar定义了三个区域:轨道,拇指和滚动条本身。 Synd 的ScrollBarUI(为JScrollBar定义的ComponentUI子类)实现将SynthStyle与这些区域中的每一个相关联。

Synth Architecture Drawing.

SynthStyle提供 Synth ComponentUI实现使用的样式信息。例如,SynthStyle定义前景色和背景色,字体信息等。此外,每个SynthStyle都有SynthPainter用于绘制区域。例如,SynthPainter定义了两种方法paintScrollBarThumbBackgroundpaintScrollBarThumbBorder,它们用于绘制滚动条拇指区域。

Synth 中的每个ComponentUIs使用SynthStyleFactory获得SynthStyles。有两种方法可以定义SynthStyleFactory:通过 Synth XML 文件或以编程方式。以下代码显示了如何加载指定 Synth-look 外观的 XML 文件,这将创建一个使用 XML 文件中的SynthStyles填充的SynthStyleFactory实现:

  1. SynthLookAndFeel laf = new SynthLookAndFeel();
  2. laf.load(MyClass.class.getResourceAsStream("laf.xml"), MyClass.class);
  3. UIManager.setLookAndFeel(laf);

程序化路线涉及创建SynthStyleFactory的实现,返回SynthStyles。以下代码创建一个自定义SynthStyleFactory,为按钮和树返回不同的SynthStyles

  1. class MyStyleFactory extends SynthStyleFactory {
  2. public SynthStyle getStyle(JComponent c, Region id) {
  3. if (id == Region.BUTTON) {
  4. return buttonStyle;
  5. }
  6. else if (id == Region.TREE) {
  7. return treeStyle;
  8. }
  9. return defaultStyle;
  10. }
  11. }
  12. SynthLookAndFeel laf = new SynthLookAndFeel();
  13. UIManager.setLookAndFeel(laf);
  14. SynthLookAndFeel.setStyleFactory(new MyStyleFactory());

XML 文件

可以在 javax.swing.plaf.synth/doc-files/synthFileFormat.html中找到 Synth XML 文件的 DTD 说明。

加载 Synth 外观时,仅呈现具有定义(与区域绑定的“样式”,如下所述)的 GUI 组件(或区域)。任何组件都没有默认行为 - 没有 Synth XML 文件中的样式定义,GUI 是一个空白画布。

要指定组件(或区域)的呈现,您的 XML 文件必须包含< style>然后,使用< bind>将绑定到该区域。元件。例如,让我们定义一个包含字体,前景色和背景色的样式,然后将该样式绑定到所有组件。在开发它时,在 Synth XML 文件中包含这样的元素是个好主意 - 那么,你还没有定义的任何组件至少会有颜色和字体:__

  1. <synth>
  2. <style id="basicStyle">
  3. <font name="Verdana" size="16"/>
  4. <state>
  5. </state>
  6. </style>
  7. <bind style="basicStyle" type="region" key=".*"/>
  8. </synth>

让我们分析一下这个样式定义:

  1. < style> element 是 Synth XML 文件的基本构建块。它包含描述区域渲染所需的所有信息。 < style> element 可以描述多个区域,就像这里所做的那样。但一般来说,最好创建一个< style>每个组件或区域的元素。请注意< style> element 被赋予一个标识符,字符串“basicStyle”。此标识符稍后将在< bind>中使用。元件。

  2. < font> < style>的元素 element 将字体设置为 Verdana,大小为 16。

  3. < state> < style>的元素元素将在下面讨论。 < state>区域的元素可以具有七个可能值中的一个或混合。如果未指定该值,则定义适用于所有状态,这是此处的意图。因此,“所有状态”的背景和前景色在此元素中定义。

  4. 最后,< style>刚刚定义的标识符“basicStyle”的元素是绑定到所有区域的。 < bind> element 将“basicStyle”绑定到“region”类型。绑定适用于哪种区域类型由“key”属性给出,在这种情况下,“。*”是“all”的正则表达式。

在创建一些工作示例之前,让我们看一下 Synth XML 文件的各个部分。我们将从< bind>开始元素,显示给定的< style>应用于组件或区域。

< bind>元件

每当一个< style>如果元素已定义,则必须在其生效之前将其绑定到一个或多个组件或区域。 < bind>元素用于此目的。它需要三个属性:

  1. style是先前定义的样式的唯一标识符。

  2. type是“名称”或“地区”。如果type是名称,请使用component.getName()方法获取名称。如果type是一个区域,请使用javax.swing.plaf.synth包中Region类中定义的适当常量。

  3. key是一个正则表达式,用于确定样式绑定到哪些组件或区域。

区域是一种识别组件或组件的一部分的方法。区域基于 Region 类中的常量,通过去掉下划线进行修改:

例如,要标识 SPLIT_PANE 区域,您将使用 SPLITPANE,splitpane 或 SplitPane(不区分大小写)。

将样式绑定到某个区域时,该样式将应用于具有该区域的所有组件。您可以将样式绑定到多个区域,并且可以将多个样式绑定到区域。例如,

  1. <style id="styleOne">
  2. <!-- styleOne definition goes here -->
  3. </style>
  4. <style id="styleTwo">
  5. <!-- styleTwo definition goes here -->
  6. </style>
  7. <bind style="styleOne" type="region" key="Button"/>
  8. <bind style="styleOne" type="region" key="RadioButton"/>
  9. <bind style="styleOne" type="region" key="ArrowButton"/>
  10. <bind style="styleTwo" type="region" key="ArrowButton"/>

您可以绑定到各个命名组件,无论它们是还是绑定为区域。例如,假设您希望 GUI 中的“确定”和“取消”按钮与所有其他按钮的处理方式不同。首先,使用component.setName()方法给出确定和取消按钮名称。然后,您将定义三种样式:一种用于一般按钮(region =“Button”),一种用于 OK 按钮(name =“OK”),另一种用于 Cancel 按钮(name =“Cancel”)。最后,您将绑定这些样式,如下所示:

  1. <bind style="styleButton" type="region" key="Button">
  2. <bind style="styleOK" type="name" key="OK">
  3. <bind style="styleCancel" type="name" key="Cancel">

结果,“OK”按钮被绑定到“styleButton”和“styleOK”,而“取消”按钮被绑定到“styleButton”和“styleCancel”。 “

当组件或区域绑定到多个样式时,将合并样式


Note:

正如样式可以绑定到多个区域或名称一样,可以将多个样式绑定到区域或名称。这些多个样式将合并为区域或名称。优先级被赋予文件中稍后定义的样式。


< state>元件

< state> element 允许您定义依赖于其“状态”的区域的外观。例如,您通常希望PRESSED按钮看起来与ENABLED状态下的按钮不同。 < state>有七个可能的值。 Synth XML DTD 中定义的。他们是:

  1. 启用
  2. 鼠标移到
  3. PRESSED
  4. 禁用
  5. FOCUSED
  6. SELECTED
  7. 默认

您还可以使用“和”分隔的复合状态,例如,ENABLED 和 FOCUSED。如果未指定值,则定义的外观将应用于所有状态。

例如,这是一种指定每个州画家的风格。所有按钮都以某种方式绘制,除非状态为“PRESSED”,在这种情况下,它们的绘制方式不同:

  1. <style id="buttonStyle">
  2. <property key="Button.textShiftOffset" type="integer" value="1"/>
  3. <insets top="10" left="10" right="10" bottom="10"/>
  4. <state>
  5. <imagePainter method="buttonBackground" path="images/button.png"
  6. sourceInsets="10 10 10 10"/>
  7. </state>
  8. <state value="PRESSED">
  9. <imagePainter method="buttonBackground" path="images/button2.png"
  10. sourceInsets="10 10 10 10"/>
  11. </state>
  12. </style>
  13. <bind style="buttonStyle" type="region" key="Button"/>

忽略< property>和< insets>目前,您可以看到按下的按钮的颜色与未按下的按钮不同。

< state>使用的值是与区域状态最匹配的已定义状态。匹配由与区域状态匹配的值的数量确定。如果没有状态值匹配,则使用没有值的状态。如果有匹配,将选择具有最多个别匹配的状态。例如,以下代码定义了三种状态:

  1. <state id="zero">
  2. </state>
  3. <state value="SELECTED and PRESSED" id="one">
  4. </state>
  5. <state value="SELECTED" id="two">
  6. </state>

如果该区域的状态至少包含 SELECTED 和 PRESSED,则将选择状态 1。如果状态包含 SELECTED,但不包含 PRESSED,则将使用状态二。如果状态既不包含 SELECTED 也不包含 PRESSED,则将使用状态零。

当前状态匹配两个状态定义的相同数量的值时,使用的那个是样式中定义的第一个。例如,MOUSE_OVER状态始终为PRESSED按钮(除非鼠标悬停在按钮上,否则无法按下按钮)。因此,如果首先声明MOUSE_OVER状态,它将始终在PRESSED上选择,并且不会为PRESSED定义任何绘画。

  1. <state value="PRESSED">
  2. <imagePainter method="buttonBackground" path="images/button_press.png"
  3. sourceInsets="9 10 9 10" />
  4. </state>
  5. <state value="MOUSE_OVER">
  6. <imagePainter method="buttonBackground" path="images/button_on.png"
  7. sourceInsets="10 10 10 10" />
  8. </state>

上面的代码将正常工作。但是,如果反转文件中MOUSE_OVERPRESSED状态的顺序,则永远不会使用PRESSED状态。这是因为PRESSED状态的任何状态都是*也是 _ a MOUSE_OVER状态。由于首先定义了MOUSE_OVER状态,因此它将被使用。

颜色和字体

<颜色> element 需要两个属性:

  1. value可以是java.awt.Color常量中的任何一个,例如 RED,WHITE,BLACK,BLUE 等。它也可以是 RGB 值的十六进制表示,例如#FF00FF 或#326A3B。

  2. type描述颜色适用的位置 - 它可以是 BACKGROUND,FOREGROUND,FOCUS,TEXT_BACKGROUND 或 TEXT_FOREGROUND。

例如:

  1. <style id="basicStyle">
  2. <state>
  3. </state>
  4. </style>

< font> element 有三个属性:

  1. name - 字体的名称。例如,Arial 或 Verdana。

  2. size - 字体的大小(以像素为单位)。

  3. style(可选)-BOLD,ITALIC 或 BOLD ITALIC。如果省略,则会得到普通字体。

例如:

  1. <style id="basicStyle">
  2. <font name="Verdana" size="16"/>
  3. </style>

每个<颜色>元素和< font> element 有一个替代用法。每个都可以具有id属性或idref属性。使用id属性,您可以使用idref属性定义稍后可以重复使用的颜色。例如,

  1. <font id="textFont" name="Verdana" size="16"/>
  2. ...
  3. ...
  4. ...
  5. <font idref="textFont"/>

插图

insets在绘制时添加到组件的大小。例如,如果没有插入,带有Cancel标题的按钮将足够大,以包含所选字体中的标题。使用< insets>像这样的元素

  1. <insets top="15" left="20" right="20" bottom="15"/>,

按钮将在标题上方和下方 15 个像素处以及标题左侧和右侧 20 个像素处变大。

绘画与图像

Synth 的文件格式允许通过图像自定义绘画。 Synth 的图像画家将图像分成九个不同的区域:顶部,右上,右下,右下,底部,左下,左,左上和中心。这些区域中的每一个都被绘制到目的地。顶部,左侧,底部和右侧边缘平铺或拉伸,而角部(sourceInsets)保持固定。


Note:

< insets>之间没有任何关系。元素和sourceInsets属性。 < insets> element 定义区域占用的空间,而sourceInsets属性定义如何绘制图像。 < insets>和sourceInsets通常是相似的,但它们不一定是。


您可以指定是否应使用paintCenter属性绘制中心区域。下图显示了九个方面:

Nine Image Areas.

我们以创建一个按钮为例。为此,我们可以使用以下图像(显示大于其实际大小):

Button Image.

左上角的红色框是 10 像素的正方形(包括框边框) - 它显示了绘画时不应拉伸的角落区域。为实现此目的,顶部和左侧sourceInsets应设置为 10.我们将使用以下样式和绑定:

  1. <style id="buttonStyle">
  2. <insets top="15" left="20" right="20" bottom="15"/>
  3. <state>
  4. <imagePainter method="buttonBackground" path="images/button.png"
  5. sourceInsets="10 10 10 10"/>
  6. </state>
  7. </style>
  8. <bind style="buttonStyle" type="region" key="button"/>

< state>内的线条 element 指定应使用图像images/button.png绘制按钮的背景。该路径相对于传递给 SynthLookAndFeel 的 load 方法的 Class。 sourceInsets属性指定图像中不要拉伸的区域。在这种情况下,顶部,左侧,底部和右侧插图均为 10.这将使画家不会在图像的每个角落处拉伸 10 x 10 像素区域。

< bind>将buttonStyle绑定到所有按钮。

< imagePainter> element 提供渲染区域的一部分所需的所有信息。它只需要几个属性:

  • method-this 指定javax.swing.plaf.synth.SynthPainter类中的哪些方法用于绘制。 SynthPainter类包含大约 100 个以paint开头的方法。当您确定需要哪一个时,删除paint前缀,将剩余的第一个字母更改为小写,并将结果用作method属性。例如,SynthPainter方法paintButtonBackground成为属性buttonBackground

  • path-要使用的图像的路径,相对于传递给 SynthLookAndFeel 的加载方法的 Class。

  • sourceInsets - 以像素为单位的 insets,表示不应拉伸的角区域的宽度和高度它们按顺序映射到顶部,左侧,底部和右侧。

  • paintCenter(可选):此属性允许您保留图像的中心或删除它(例如,在文本字段中,可以绘制文本)。

下面的列表显示了用于根据< state>加载不同图像的 XML 代码。的按钮

  1. <style id="buttonStyle">
  2. <property key="Button.textShiftOffset" type="integer" value="1"/>
  3. <insets top="15" left="20" right="20" bottom="15"/>
  4. <state>
  5. <imagePainter method="buttonBackground" path="images/button.png"
  6. sourceInsets="10 10 10 10"/>
  7. </state>
  8. <state value="PRESSED">
  9. <imagePainter method="buttonBackground" path="images/button2.png"
  10. sourceInsets="10 10 10 10"/>
  11. </state>
  12. </style>
  13. <bind style="buttonStyle" type="region" key="button"/>

button2.png 显示 button.png 的郁闷版本,向右移动一个像素。这条线

  1. <property key="Button.textShiftOffset" type="integer" value="1"/>

相应地移动按钮文本,如下一节中所述。

< property>元件

<性>元素用于将键值对添加到< style>元件。许多组件使用键值对来配置其视觉外观。

< property> element 有三个属性:

  • key - 财产的名称。

  • type - 属性的数据类型。

  • value - 财产的价值。

有一个属性表(componentProperties.html),列出了每个组件支持的属性: javax/swing/plaf/synth/doc-files/componentProperties.html

由于 button2.png 图像在按下时将视觉按钮移动一个像素,我们也应该移动按钮文本。有一个按钮属性执行此操作:

  1. <property key="Button.textShiftOffset" type="integer" value="1"/>

一个例子

这是一个例子,使用上面定义的按钮样式。按钮样式,加上“支持样式”,其中包含绑定到所有区域的字体和颜色的定义(类似于上面标题为“XML 文件”一节中显示的“basicStyle”),它们组合在 中]buttonSkin.xml``以下是buttonSkin.xml的清单:

  1. <!-- Synth skin that includes an image for buttons -->
  2. <synth>
  3. <!-- Style that all regions will use -->
  4. <style id="backingStyle">
  5. <!-- Make all the regions that use this skin opaque-->
  6. <opaque value="TRUE"/>
  7. <font name="Dialog" size="12"/>
  8. <state>
  9. <!-- Provide default colors -->
  10. </state>
  11. </style>
  12. <bind style="backingStyle" type="region" key=".*"/>
  13. <style id="buttonStyle">
  14. <!-- Shift the text one pixel when pressed -->
  15. <property key="Button.textShiftOffset" type="integer" value="1"/>
  16. <insets top="15" left="20" right="20" bottom="15"/>
  17. <state>
  18. <imagePainter method="buttonBackground" path="images/button.png"
  19. sourceInsets="10 10 10 10"/>
  20. </state>
  21. <state value="PRESSED">
  22. <imagePainter method="buttonBackground" path="images/button2.png"
  23. sourceInsets="10 10 10 10"/>
  24. </state>
  25. </style>
  26. <!-- Bind buttonStyle to all JButtons -->
  27. <bind style="buttonStyle" type="region" key="button"/>
  28. </synth>

我们可以加载这个 XML 文件,以便为名为SynthApplication.java的简单应用程序使用 Synth 外观。该应用程序的 GUI 包括一个按钮和一个标签。每次单击该按钮,标签都会递增。


Note:

即使buttonSkin.xml不包含样式,标签也会被绘制。这是因为有一个包含字体和颜色的通用“backingStyle”。


这是 SynthApplication.java文件的列表。


Try this:

单击“启动”按钮以使用 Java™Web Start下载 JDK 7 或更高版本)运行 SynthApplication 示例。或者,要自己编译并运行示例,请参考示例索引

Launches the SynthApplication example


绘画与图标

单选按钮和复选框通常通过固定大小的图标呈现其状态。对于这些,您可以创建一个图标并将其绑定到相应的属性(请参阅属性表, javax/swing/plaf/synth/doc-files/componentProperties.html )。例如,要绘制选中或未选中的单选按钮,请使用以下代码:

  1. <style id="radioButton">
  2. <imageIcon id="radio_off" path="images/radio_button_off.png"/>
  3. <imageIcon id="radio_on" path="images/radio_button_on.png"/>
  4. <property key="RadioButton.icon" value="radio_off"/>
  5. <state value="SELECTED">
  6. <property key="RadioButton.icon" value="radio_on"/>
  7. </state>
  8. </style>
  9. <bind style="radioButton" type="region" key="RadioButton"/>

自定义画家

Synth 的文件格式允许通过 long-term persistence of JavaBeans components 嵌入任意对象。除了 Synth 提供的基于图像的画家之外,这种能力特别有用。例如,以下 XML 代码指定应在文本字段的背景中呈现渐变:

  1. <synth>
  2. <object id="gradient" class="GradientPainter"/>
  3. <style id="textfield">
  4. <painter method="textFieldBackground" idref="gradient"/>
  5. </style>
  6. <bind style="textfield" type="region" key="textfield"/>
  7. </synth>

GradientPainter 类看起来像这样:

  1. public class GradientPainter extends SynthPainter {
  2. public void paintTextFieldBackground(SynthContext context,
  3. Graphics g, int x, int y,
  4. int w, int h) {
  5. // For simplicity this always recreates the GradientPaint. In a
  6. // real app you should cache this to avoid garbage.
  7. Graphics2D g2 = (Graphics2D)g;
  8. g2.setPaint(new GradientPaint((float)x, (float)y, Color.WHITE,
  9. (float)(x + w), (float)(y + h), Color.RED));
  10. g2.fillRect(x, y, w, h);
  11. g2.setPaint(null);
  12. }
  13. }

结论

在本课程中,我们介绍了使用javax.swing.plaf.synth包创建自定义外观。本课程的重点是使用外部 XML 文件来定义外观。下一课将介绍一个示例应用程序,该应用程序使用带有 XML 文件的 Synth 框架创建搜索对话框。