技术开发 频道

创建用于翻转效果的 Java2D 合成

  【IT168 技术文章】

         引言

  许多应用程序都有包含图标的按钮,当按钮被选中或者鼠标移到按钮上时,按钮的图标会发生改变。Web 浏览器(如 Microsoft Internet Explorer)中的工具栏按钮就是这种现象的一个示例,其中的按钮图形是灰色的,当鼠标移动到按钮上时,图形会变成有颜色的。为了实现这种效果,要创建两组图形;一组用于正常状态,一组用于当鼠标移动到按钮上时的活动状态。然而,对于设计者来说,创建两组图形很费时间,并且意味着在需要更改图形时,必须对这两组图形做双倍的维护工作。更优化的办法是只使用一个图标,而图形效果则通过编程来实现,从而避免创建和维护一个单独图标的开销。本文描述的问题是针对如 图 1所示的向导页中的一组按钮的,当用户在每个按钮上悬停时,图形会发生改变以向用户表示该按钮是活动的。

  图 1.Web Module 按钮在鼠标移动到它上面时处于活动状态并且图形已经改变。

  在本文中,我们将说明如何通过使用 Java2D API 创建一个能够接受图像并创建所期望的效果的类来实现上述效果。这需要我们理解如何构造图像以及如何使用 AWT composite 来操作这些图像。

  背景知识

  javax.swing.JButton 类有一个布尔属性 rolloverEnabled 和一个 rolloverIcon 属性,后者的类型是 javax.swing.Icon。如果 rolloverEnabled 为 true,则当鼠标移动到按钮上时,rolloverIcon 的值就被用到该按钮的图形上。我们认定,一个优秀的解决方案应该有一个新的称为 RolloverImageIcon 的类,我们将在其构造函数中给它提供一张图像,然后,这个类会在绘制图标之前对图标进行操作。

  这样,用于创建 rolloverButton 的代码将如下所示:

1 JButton button = new JButton(regularIcon);
2 button.setRolloverEnabled(true);
3 button.setRolloverIcon(new RolloverIcon(regularIcon));
4

 

  下一步是创建能够将初始图标包装起来并用 图 1所示的图形效果给图标着色的 RolloverIcon 类。

  RolloverIcon

  RolloverIcon 类将实现 javax.swing.Icon 接口,这个接口允许按钮的 rolloverIcon 属性取有效值并将初始图标存储到名为 fIcon 的实例变量中:

  public class RolloverIcon implements Icon { protected Icon fIcon; {

  一种变通的解决方案可能是创建 JButton 的子类,并将翻转效果封装到这个子类中,不过,通过将逻辑放到 RolloverIcon 类中,图形效果也可以在其它情形(如复选框或菜单项)中使用。假设想得到定制的子类,那么使用创建图形效果的逻辑并将这个逻辑委托给 RolloverIcon 的实例是个很简单的做法。

  javax.swing.Icon 接口有三个方法: getIconWidth()、getIconHeight() 和 paintIcon(Component,Graphics,x,y) 。前两个方法可以委托给 RolloverIcon 正在为它创建图形效果的图标,因为翻转图像的大小将与原来图像的相同。

1 public int getIconHeight() {
2   return fIcon.getIconHeight();
3 }
4 public int getIconWidth() {
5   return fIcon.getIconWidth();
6 }
7

 

  绘制(paint)方法是将对图形上下文进行实际绘图的方法。Component 参数代表正在为之绘制图标的控件,如 javax.swing.JButton 的实例。这个方法允许我们访问诸如控件的字体、插图等细节问题,以及我们在绘制图标时可能想考虑的其它属性。x 和 y 参数是在绘图表面上正在对该图形进行着色的位置。这些位置是绘图表面的绝对位置,与从组件的 getLocation() 返回的值不同,后者是按钮相对于其父容器的位置。绘图 API 需要使用绝对值,以便将它们传递到 paintIcon 方法中,避免不得不遍历组件的所有父容器来计算这些值。

  graphics 参数是代表绘图表面的对象。虽然它的类型是 java.awt.Graphics,但它将是 java.awt.Graphics2D 的实例。Graphics2D 抽象类是 Java2D API 的一部分,它是出于向后兼容的目的作为 Java2 平台的一部分引入的,绘制方法的参数没有被重新转换为 Graphics2D 类型,尽管只要使用的 JRE 是 Java2 或更高版本就可以保证这个参数是 java.awt.Graphics2D 的实例。

  paintIcon 方法的完全说明如下所示:

 

  public void paintIcon(Component c, Graphics g, int x, int y);

  RolloverIcon 实例将包装我们希望用 图 1中所示的图形效果绘制的初始图标。我们可以使用 Graphics2D 对象的 composite 属性做到这一点。composite 属性的类型是 java.awt.Composite 接口,并且由 Graphics2D 对象完成的对图形表面的所有图元绘图都直接通过对象的 composite 进行。存在许多现成的合成类,如其中有一个是用于创建异或(XOR)效果的。XORComposite 是 sun.java2d.loops.XORComposite 类,它的构造函数以要着色成异或效果的绘图的颜色为参数。每种颜色由红、绿和蓝值组成,如果用黑色(黑色的 r、g、b 值为 0、0、0)对颜色进行异或,则该颜色将被反转。要看到这种效果,绘制方法可以编写成如下所示:

1 public void paintIcon(Component c, Graphics g, int x, int y) {
2   Graphics2D g2D = (Graphics2D)g;
3   Composite oldComposite = g2D.getComposite();
4   g2D.setComposite(new sun.java2d.loops.XORComposite(Color.black));
5   fIcon.paintIcon(c,g,x,y);
6   g2D.setComposite(oldComposite);
7 }
8

  要对初始图标(保存在实例变量 fIcon 中)进行着色,我们只要遵从它的 paintIcon(Component,Graphics,int,int) 方法(这个方法已经将图形的 composite 预先设置为 XORComposite 对象)就行了。在操作图形对象的属性时,在完成之后将这些属性恢复成原来的值是一种很好的做法。在上面的方法中显示了这一点,我们在修改 composite 之前先将原来的 composite 存储起来,然后在完成修改之后将它恢复。如果您没有这样做,则 XORComposite 将会被保留在图形对象中,并且会影响到随后的所有绘图操作。

  图 2显示了 XORComposite 的效果。上面那行按钮是 RolloverIcon 被设置成 rolloverIcon 属性的原来的按钮。下面那行按钮是被永久性地设置的,用来显示被设置成 icon 属性的 RolloverIcon 的结果。XORComposite 接受图标并通过用 0 对图标的每个像素进行异或来转换它。

         图 2. XORComposite 可以用来控制如何对图标进行着色 

  异或的效果虽然不是如 图 1 所示的结果,但它确实表明了 composite 是如何负责在图标的绘图表面上进行着色的。如果我们创建自己的能让我们访问图标的精确着色的合成类,那我们就应该能够实现我们所期望的翻转效果。我们的类将叫做 RolloverComposite ,并且 paintIcon 方法可以在对初始图标进行着色之前将所期望的效果在图形对象中进行设置。

  在实现 RolloverComposite 类之前,我们需要了解关于在 Java 语言中如何表示颜色以及如何将这些颜色绘制到绘图表面上去的更多知识。

  RolloverComposite 必须实现 java.awt.Composite 接口。这个类有一个单独的 Entry Helpers 方法,该方法返回 java.awt.CompositeContext 的实例。RolloverComposite 不负责对图形表面上的像素的实际图元进行操作,而是把这项任务委托给能够在多线程环境中维护状态和工作的合成上下文对象,因为针对单个合成可以存在多个合成上下文对象。

  ColorModel 抽象类具有能在颜色组件和基本的红、绿、蓝以及 alpha 组件之间进行转换的 API。 RenderingHints 是来自 Graphics2D 的着色提示,RolloverComposite 在需要时可以使用它,例如,在进行文本着色时是否要使用抗锯齿,或者应该使用什么类型的行合并样式。 CompositeContext 是实际操作原始像素的线程安全的对象,我们将创建一个实现这个目的的匿名内部类,如下所示:

1        // Get the source pixels
2             int[] srcPixels = new int[4];
3             src.getPixel(x,y,srcPixels);
4             // Ignore transparent pixels
5             if (srcPixels[3] != 0){
6                 // Lighten each color by 1/2, and increasing the blue
7                 srcPixels[0] = srcPixels[0] / 2;
8                 srcPixels[1] = srcPixels[1] / 2;
9                 srcPixels[2] = srcPixels[2] / 2 + 68;
10                 dstOut.setPixel(x,y,srcPixels);
11             }
12         }
13     };
14 }
15

 

  CompositeContext 接口有两个方法,即 dispose() 和 compose(Raster,Raster,WritableRaster) 。dispose 方法让我们清除任何已经分配的资源,而 compose 方法则负责图元绘图的方法。我们没有任何需要清除的对象,所以可以有一个 dispose() 的不进行任何操作的实现, compose(...) 方法则是我们对像素进行操作的地方。

  compose(...) 方法的三个参数是,用于源图像的 Raster、用于目标图像的 Raster 以及表示图形表面上将被着色的输出的 WritableRaster 。要访问给定位置的原始像素,可以使用 getPixel(int x, int y, pixel int[]) 方法。int[] 参数是一个由四个参数组成的数组,表示像素的红、绿、蓝以及 alpha 值。src 参数包含源参数的光栅, dstOut 参数则是具有 setPixel(int x, int y, pixel int[]) 方法的 WritableRaster。通过反复操作来自源图像的每个像素,我们就可以创建具有完成操作后的值的新像素,并将它设置到 dstOut 中,这个 dstOut 将被绘制到图形表面上。compose 方法的代码如下所示:

  请注意,我们检查了第四个像素 (srcPixels[3]) ,看看它是不是透明的。如果它在源图像上时是透明的,则我们就不写任何内容到 dstOut 中,因为我们想让绘图表面现有的像素保持不变。

  完成代码

  创建了返回 CompositeContext (它负责把所期望的像素值设置到要被绘制到图形表面上的光栅中)的 RolloverComposite 类之后,我们需要在 RolloverIcon 类中完成代码。早些时候我们在测试异或合成时,我们要在通过 paintIcon(...) 方法将它设置到图形的 composite 中之前先创建一个新实例。在 Java 语言中,创建对象的开销很大,因为必须分配内存,然后在不再需要时对所得到的对象进行垃圾回收,所以,我们将改用可以根据需要进行重用的 RolloverComposite 类的单子(singleton)实例。我们不必担心多个图标或图形对象会具有对同一个对象的访问权,因为所有的状态都由 CompositeContext 保存,CompositeContext 可以根据需要重新创建并能够提供线程安全的访问。

  RolloverComposite 将在叫做 DEFAULT 的公共静态(public static)域包含它的单个实例。为了迫使人们使用这个单子实例(singleton)而不是每次创建一个新对象,构造函数被声明成私有的(private),这样便只有 RolloverComposite 类能够创建它自己的实例。

1 public class RolloverComposite implements Composite {
2   public static RolloverComposite DEFAULT = new RolloverComposite();
3   private RolloverComposite(){ }
4

 

  RolloverIcon 的 paintIcon 方法在绘制它包装的初始图标之前需要将图形参数的 composite 设置成单子 RolloverComposite 。绘制完成后需要恢复原来的 composite,以确保随后的任何绘图操作不会受到由让定制的 composite 实例保留超过所需要的时间所带来的不必要影响。

1 public void paintIcon(Component c, Graphics g, int x, int y) {
2   Graphics2D g2D = (Graphics2D)g;
3   Composite oldComposite = g2D.getComposite();
4   g2D.setComposite(RolloverComposite.DEFAULT);
5   fIcon.paintIcon(c,g,x,y);
6   g2D.setComposite(oldComposite);
7 }
8

 

  完成的结果

  编写完 RolloverIcon 类的代码后,现在我们需要测试它。为此,我们编写了一个测试工具,它创建两行按钮,每行有四个,其中上面那行具有翻转效果,下面那行按钮的图标被永久性地设置成翻转图标。您可以从下面的 参考资料部分下载这个测试工具的代码以及 RolloverIcon 和 RolloverComposite 类的所有代码。

  图 3. 测试工具显示了 RolloverIcon 的效果。

  结束语

  RolloverIcon 是 Java2D API 的可扩展性有多么好的一个示例,这样我们便可以访问图形子系统中级别很低的组件,从而创建我们自己的扩展。对图形对象所使用的合成进行控制是我们用来很好地实现图形效果的办法,但对 Java2D 的工作机制有了更深的理解后,我们还可以实现许多其它的定制效果。

0
相关文章