如何使用 JLayer 类装饰组件

原文: https://docs.oracle.com/javase/tutorial/uiswing/misc/jlayer.html

JLayer类是 Swing 组件的灵活且强大的装饰器。它使您能够在不修改底层组件的情况下绘制组件并响应组件事件。

本文档描述了显示JLayer类功能的示例。完整的源代码可用。

有关此页面上材料的简要介绍,请观看以下视频。

<iframe frameborder=”1” height=”349” longdesc=”a video on youtube that demonstrates the capabilities of the jlayer component” src=”http://www.youtube.com/embed/6mQYsWCkx4g“ title=”Using JLayer in Swing Applications” width=”560”></iframe>

视频需要支持 JavaScript 的 Web 浏览器和 Internet 连接。如果您看不到该视频,请尝试在 YouTube 上观看。

javax.swing.JLayer级是团队的一半。另一半是javax.swing.plaf.LayerUI级。假设你想在JButton对象上做一些自定义绘图(装饰 _ JButton对象)。您要装饰的组件是目标*。

  • 创建目标组件。
  • 创建LayerUI子类的实例以进行绘制。
  • 创建一个包裹目标和LayerUI对象的JLayer对象。
  • 使用用户界面中的JLayer对象就像使用目标组件一样。

例如,要将JPanel子类的实例添加到JFrame对象,您可以执行与此类似的操作:

  1. JFrame f = new JFrame();
  2. JPanel panel = createPanel();
  3. f.add (panel);

要装饰JPanel对象,请执行与此类似的操作:

  1. JFrame f = new JFrame();
  2. JPanel panel = createPanel();
  3. LayerUI<JPanel> layerUI = new MyLayerUISubclass();
  4. JLayer<JPanel> jlayer = new JLayer<JPanel>(panel, layerUI);
  5. f.add (jlayer);

使用泛型可确保JPanel对象和LayerUI对象用于兼容类型。在前面的示例中,JLayer对象和LayerUI对象都与JPanel类一起使用。

JLayer类通常使用其视图组件的确切类型进行生成,而LayerUI类旨在与其通用参数或其任何祖先的JLayer类一起使用。

例如,LayerUI&lt;JComponent>对象可以与JLayer&lt;AbstractButton>对象一起使用。

LayerUI对象负责JLayer对象的自定义装饰和事件处理。当您创建LayerUI子类的实例时,您的自定义行为可适用于具有适当泛型类型的每个JLayer对象。这就是JLayer类是final的原因;所有自定义行为都封装在LayerUI子类中,因此不需要创建JLayer子类。

LayerUI类从ComponentUI类继承其大部分行为。以下是最常被覆盖的方法:

  • 需要绘制目标组件时调用paint(Graphics g, JComponent c)方法。要以与 Swing 呈现它相同的方式呈现组件,请调用super.paint(g, c)方法。
  • LayerUI子类的实例与组件关联时,将调用installUI(JComponent c)方法。在此执行任何必要的初始化。传入的组件是相应的JLayer对象。使用JLayer类’getView()方法检索目标组件。
  • LayerUI子类的实例不再与给定组件关联时,将调用uninstallUI(JComponent c)方法。如有必要,请在这里清理。

要使用JLayer类,需要一个好的LayerUI子类。最简单的LayerUI类改变了组件的绘制方式。例如,这是一个在组件上绘制透明颜色渐变的图形。

  1. class WallpaperLayerUI extends LayerUI<JComponent> {
  2. @Override
  3. public void paint(Graphics g, JComponent c) {
  4. super.paint(g, c);
  5. Graphics2D g2 = (Graphics2D) g.create();
  6. int w = c.getWidth();
  7. int h = c.getHeight();
  8. g2.setComposite(AlphaComposite.getInstance(
  9. AlphaComposite.SRC_OVER, .5f));
  10. g2.setPaint(new GradientPaint(0, 0, Color.yellow, 0, h, Color.red));
  11. g2.fillRect(0, 0, w, h);
  12. g2.dispose();
  13. }
  14. }

paint()方法是进行自定义绘图的地方。对super.paint()方法的调用绘制了JPanel对象的内容。设置 50%透明复合材料后,绘制颜色渐变。

在定义LayerUI子类之后,使用它很简单。以下是一些使用WallpaperLayerUI类的源代码:

  1. import java.awt.*;
  2. import javax.swing.*;
  3. import javax.swing.plaf.LayerUI;
  4. public class Wallpaper {
  5. public static void main(String[] args) {
  6. javax.swing.SwingUtilities.invokeLater(new Runnable() {
  7. public void run() {
  8. createUI();
  9. }
  10. });
  11. }
  12. public static void createUI() {
  13. JFrame f = new JFrame("Wallpaper");
  14. JPanel panel = createPanel();
  15. LayerUI<JComponent> layerUI = new WallpaperLayerUI();
  16. JLayer<JComponent> jlayer = new JLayer<JComponent>(panel, layerUI);
  17. f.add (jlayer);
  18. f.setSize(300, 200);
  19. f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
  20. f.setLocationRelativeTo (null);
  21. f.setVisible (true);
  22. }
  23. private static JPanel createPanel() {
  24. JPanel p = new JPanel();
  25. ButtonGroup entreeGroup = new ButtonGroup();
  26. JRadioButton radioButton;
  27. p.add(radioButton = new JRadioButton("Beef", true));
  28. entreeGroup.add(radioButton);
  29. p.add(radioButton = new JRadioButton("Chicken"));
  30. entreeGroup.add(radioButton);
  31. p.add(radioButton = new JRadioButton("Vegetable"));
  32. entreeGroup.add(radioButton);
  33. p.add(new JCheckBox("Ketchup"));
  34. p.add(new JCheckBox("Mustard"));
  35. p.add(new JCheckBox("Pickles"));
  36. p.add(new JLabel("Special requests:"));
  37. p.add(new JTextField(20));
  38. JButton orderButton = new JButton("Place Order");
  39. p.add(orderButton);
  40. return p;
  41. }
  42. }

结果如下:

A panel with a jazzy decoration

源代码:

Wallpaper NetBeans Project

Wallpaper.java

使用 Java Web Start 运行:

Launches the example

LayerUI类’paint()方法使您可以完全控制如何绘制组件。这是另一个LayerUI子类,它显示了如何使用 Java 2D 图像处理修改面板的整个内容:

  1. class BlurLayerUI extends LayerUI<JComponent> {
  2. private BufferedImage mOffscreenImage;
  3. private BufferedImageOp mOperation;
  4. public BlurLayerUI() {
  5. float ninth = 1.0f / 9.0f;
  6. float[] blurKernel = {
  7. ninth, ninth, ninth,
  8. ninth, ninth, ninth,
  9. ninth, ninth, ninth
  10. };
  11. mOperation = new ConvolveOp(
  12. new Kernel(3, 3, blurKernel),
  13. ConvolveOp.EDGE_NO_OP, null);
  14. }
  15. @Override
  16. public void paint (Graphics g, JComponent c) {
  17. int w = c.getWidth();
  18. int h = c.getHeight();
  19. if (w == 0 || h == 0) {
  20. return;
  21. }
  22. // Only create the offscreen image if the one we have
  23. // is the wrong size.
  24. if (mOffscreenImage == null ||
  25. mOffscreenImage.getWidth() != w ||
  26. mOffscreenImage.getHeight() != h) {
  27. mOffscreenImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
  28. }
  29. Graphics2D ig2 = mOffscreenImage.createGraphics();
  30. ig2.setClip(g.getClip());
  31. super.paint(ig2, c);
  32. ig2.dispose();
  33. Graphics2D g2 = (Graphics2D)g;
  34. g2.drawImage(mOffscreenImage, mOperation, 0, 0);
  35. }
  36. }

paint()方法中,面板渲染为屏幕外图像。使用卷积运算符处理屏幕外图像,然后将其绘制到屏幕上。

整个用户界面仍然有效,只是模糊:

A graphically inverted user interface

源代码:

Myopia NetBeans Project

Myopia.java

使用 Java Web Start 运行:

Launches the example

您的LayerUI子类也可以接收其相应组件的所有事件。但是,JLayer实例必须注册其对特定类型事件的兴趣。这种情况发生在JLayer类’setLayerEventMask()方法中。但是,通常,此调用是在LayerUI类’installUI()方法中执行的初始化。

例如,以下摘录显示了LayerUI子类的一部分,它注册接收鼠标和鼠标运动事件。

  1. public void installUI(JComponent c) {
  2. super.installUI(c);
  3. JLayer jlayer = (JLayer)c;
  4. jlayer.setLayerEventMask(
  5. AWTEvent.MOUSE_EVENT_MASK |
  6. AWTEvent.MOUSE_MOTION_EVENT_MASK
  7. );
  8. }

进入JLayer子类的所有事件都将路由到名称与事件类型匹配的事件处理器方法。例如,您可以通过覆盖相应的方法来响应鼠标和鼠标运动事件:

  1. protected void processMouseEvent(MouseEvent e, JLayer l) {
  2. // ...
  3. }
  4. protected void processMouseMotionEvent(MouseEvent e, JLayer l) {
  5. // ...
  6. }

以下是LayerUI子类,在鼠标在面板内移动的任何地方绘制半透明圆。

  1. class SpotlightLayerUI extends LayerUI<JPanel> {
  2. private boolean mActive;
  3. private int mX, mY;
  4. @Override
  5. public void installUI(JComponent c) {
  6. super.installUI(c);
  7. JLayer jlayer = (JLayer)c;
  8. jlayer.setLayerEventMask(
  9. AWTEvent.MOUSE_EVENT_MASK |
  10. AWTEvent.MOUSE_MOTION_EVENT_MASK
  11. );
  12. }
  13. @Override
  14. public void uninstallUI(JComponent c) {
  15. JLayer jlayer = (JLayer)c;
  16. jlayer.setLayerEventMask(0);
  17. super.uninstallUI(c);
  18. }
  19. @Override
  20. public void paint (Graphics g, JComponent c) {
  21. Graphics2D g2 = (Graphics2D)g.create();
  22. // Paint the view.
  23. super.paint (g2, c);
  24. if (mActive) {
  25. // Create a radial gradient, transparent in the middle.
  26. java.awt.geom.Point2D center = new java.awt.geom.Point2D.Float(mX, mY);
  27. float radius = 72;
  28. float[] dist = {0.0f, 1.0f};
  29. Color[] colors = {new Color(0.0f, 0.0f, 0.0f, 0.0f), Color.BLACK};
  30. RadialGradientPaint p =
  31. new RadialGradientPaint(center, radius, dist, colors);
  32. g2.setPaint(p);
  33. g2.setComposite(AlphaComposite.getInstance(
  34. AlphaComposite.SRC_OVER, .6f));
  35. g2.fillRect(0, 0, c.getWidth(), c.getHeight());
  36. }
  37. g2.dispose();
  38. }
  39. @Override
  40. protected void processMouseEvent(MouseEvent e, JLayer l) {
  41. if (e.getID() == MouseEvent.MOUSE_ENTERED) mActive = true;
  42. if (e.getID() == MouseEvent.MOUSE_EXITED) mActive = false;
  43. l.repaint();
  44. }
  45. @Override
  46. protected void processMouseMotionEvent(MouseEvent e, JLayer l) {
  47. Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l);
  48. mX = p.x;
  49. mY = p.y;
  50. l.repaint();
  51. }
  52. }

mActive变量指示鼠标是否在面板坐标内。在installUI()方法中,调用setLayerEventMask()方法以指示LayerUI子类对接收鼠标和鼠标运动事件的兴趣。

processMouseEvent()方法中,根据鼠标的位置设置mActive标志。在processMouseMotionEvent()方法中,鼠标移动的坐标存储在mXmY成员变量中,以便稍后可以在paint()方法中使用它们。

paint()方法显示面板的默认外观,然后覆盖聚光灯效果的径向渐变:

A spotlight that follows the mouse

源代码:

Diva NetBeans Project

Diva.java

使用 Java Web Start 运行:

Launches the example

此示例是动画繁忙指示符。它演示了LayerUI子类中的动画,并具有淡入和淡出功能。前面的例子比较复杂,但它基于定义自定义绘图的paint()方法的相同原理。

单击下订单按钮,查看忙碌指示 4 秒钟。注意面板是如何变灰并且指示器旋转。指标的要素具有不同的透明度。

LayerUI子类WaitLayerUI类显示了如何触发属性更改事件以更新组件。 WaitLayerUI类使用Timer对象每秒 24 次更新其状态。这发生在计时器的目标方法actionPerformed()方法中。

actionPerformed()方法使用firePropertyChange()方法指示内部状态已更新。这会触发对applyPropertyChange()方法的调用,该方法重新绘制JLayer对象:

A smooth busy indicator

源代码:

TapTapTap NetBeans Project

TapTapTap.java

使用 Java Web Start 运行:

Launches the example

本文档的最后一个示例显示了如何使用JLayer类来装饰文本字段以显示它们是否包含有效数据。虽然其他示例使用JLayer类来包装面板或常规组件,但此示例显示了如何专门包装JFormattedTextField组件。它还演示了单个LayerUI子类实现可用于多个JLayer实例。

JLayer类用于为具有无效数据的字段提供可视指示。当ValidationLayerUI类绘制文本字段时,如果无法解析字段内容,则会绘制红色 X.这是一个例子:

Immediate feedback for bad input

源代码:

FieldValidator NetBeans Project

FieldValidator.java

使用 Java Web Start 运行:

Launches the example