如何使用 JLayer 类装饰组件

JLayer类是 Swing 组件的灵活而强大的装饰器。它使您能够利用组件并响应组件事件,而无需直接修改基础组件。

本文档介绍了展示JLayer类功能的示例。完整的源代码可用。

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

http://www.youtube.com/embed/6mQYsWCkx4g

视频需要启用 JavaScript 的 Web 浏览器和 Internet 连接。如果看不到视频,请try在 YouTube 上观看

使用 JLayer 类

javax.swing.JLayer类是团队的一半。另一半是javax.swing.plaf.LayerUI类。假设您要在JButton对象上方进行一些自定义绘制(装饰JButton对象)。要装饰的组件是目标。

  • 创建目标组件。

  • 创建LayerUI子类的实例以进行绘制。

  • 创建一个用于包装目标和LayerUI对象的JLayer对象。

  • 就像使用目标组件一样,在用户interface中使用JLayer对象。

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

JFrame f = new JFrame();

JPanel panel = createPanel();

f.add (panel);

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

JFrame f = new JFrame();

JPanel panel = createPanel();
LayerUI<JPanel> layerUI = new MyLayerUISubclass();
JLayer<JPanel> jlayer = new JLayer<JPanel>(panel, layerUI);

f.add (jlayer);

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

JLayer类通常使用其视图组件的确切类型来生成,而LayerUI类则设计用于其通用参数或其任何祖先的JLayer类。

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

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

使用 LayerUI 类

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

  • 当需要绘制目标组件时,将调用paint(Graphics g, JComponent c)方法。要以与 Swing 呈现组件相同的方式呈现组件,请调用super.paint(g, c)方法。

  • LayerUI子类的实例与组件关联时,将调用installUI(JComponent c)方法。在这里执行任何必要的初始化。传入的组件是相应的JLayer对象。使用JLayer类的getView()方法检索目标组件。

  • LayerUI子类的实例不再与给定组件关联时,将调用uninstallUI(JComponent c)方法。如有必要,请在此处清理。

绘制 Component

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

class WallpaperLayerUI extends LayerUI<JComponent> {
  @Override
  public void paint(Graphics g, JComponent c) {
    super.paint(g, c);

    Graphics2D g2 = (Graphics2D) g.create();

    int w = c.getWidth();
    int h = c.getHeight();
    g2.setComposite(AlphaComposite.getInstance(
            AlphaComposite.SRC_OVER, .5f));
    g2.setPaint(new GradientPaint(0, 0, Color.yellow, 0, h, Color.red));
    g2.fillRect(0, 0, w, h);

    g2.dispose();
  }
}

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

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

import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.LayerUI;

public class Wallpaper {
  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        createUI();
      }
    });
  }

  public static void createUI() {
    JFrame f = new JFrame("Wallpaper");
    
    JPanel panel = createPanel();
    LayerUI<JComponent> layerUI = new WallpaperLayerUI();
    JLayer<JComponent> jlayer = new JLayer<JComponent>(panel, layerUI);
    
    f.add (jlayer);
    
    f.setSize(300, 200);
    f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
    f.setLocationRelativeTo (null);
    f.setVisible (true);
  }

  private static JPanel createPanel() {
    JPanel p = new JPanel();

    ButtonGroup entreeGroup = new ButtonGroup();
    JRadioButton radioButton;
    p.add(radioButton = new JRadioButton("Beef", true));
    entreeGroup.add(radioButton);
    p.add(radioButton = new JRadioButton("Chicken"));
    entreeGroup.add(radioButton);
    p.add(radioButton = new JRadioButton("Vegetable"));
    entreeGroup.add(radioButton);

    p.add(new JCheckBox("Ketchup"));
    p.add(new JCheckBox("Mustard"));
    p.add(new JCheckBox("Pickles"));

    p.add(new JLabel("Special requests:"));
    p.add(new JTextField(20));

    JButton orderButton = new JButton("Place Order");
    p.add(orderButton);

    return p;
  }
}

结果如下:

装饰有爵士乐的面板

Source code:

Java Web Start一起运行:

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

class BlurLayerUI extends LayerUI<JComponent> {
  private BufferedImage mOffscreenImage;
  private BufferedImageOp mOperation;

  public BlurLayerUI() {
    float ninth = 1.0f / 9.0f;
    float[] blurKernel = {
      ninth, ninth, ninth,
      ninth, ninth, ninth,
      ninth, ninth, ninth
    };
    mOperation = new ConvolveOp(
            new Kernel(3, 3, blurKernel),
            ConvolveOp.EDGE_NO_OP, null);
  }

  @Override
  public void paint (Graphics g, JComponent c) {
    int w = c.getWidth();
    int h = c.getHeight();

    if (w == 0 || h == 0) {
      return;
    }

    // Only create the offscreen image if the one we have
    // is the wrong size.
    if (mOffscreenImage == null ||
            mOffscreenImage.getWidth() != w ||
            mOffscreenImage.getHeight() != h) {
      mOffscreenImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
    }

    Graphics2D ig2 = mOffscreenImage.createGraphics();
    ig2.setClip(g.getClip());
    super.paint(ig2, c);
    ig2.dispose();

    Graphics2D g2 = (Graphics2D)g;
    g2.drawImage(mOffscreenImage, mOperation, 0, 0);
  }
}

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

整个用户interface仍处于活动状态,只是模糊:

图形化的反向用户interface

Source code:

Java Web Start一起运行:

回应事件

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

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

public void installUI(JComponent c) {
  super.installUI(c);
  JLayer jlayer = (JLayer)c;
  jlayer.setLayerEventMask(
    AWTEvent.MOUSE_EVENT_MASK |
    AWTEvent.MOUSE_MOTION_EVENT_MASK
  );
}

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

protected void processMouseEvent(MouseEvent e, JLayer l) {
  // ...
}

protected void processMouseMotionEvent(MouseEvent e, JLayer l) {
  // ...
}

以下是一个LayerUI子类,该子类在鼠标在面板内移动的任何位置绘制一个半透明的圆。

class SpotlightLayerUI extends LayerUI<JPanel> {
  private boolean mActive;
  private int mX, mY;

  @Override
  public void installUI(JComponent c) {
    super.installUI(c);
    JLayer jlayer = (JLayer)c;
    jlayer.setLayerEventMask(
      AWTEvent.MOUSE_EVENT_MASK |
      AWTEvent.MOUSE_MOTION_EVENT_MASK
    );
  }

  @Override
  public void uninstallUI(JComponent c) {
    JLayer jlayer = (JLayer)c;
    jlayer.setLayerEventMask(0);
    super.uninstallUI(c);
  }

  @Override
  public void paint (Graphics g, JComponent c) {
    Graphics2D g2 = (Graphics2D)g.create();

    // Paint the view.
    super.paint (g2, c);

    if (mActive) {
      // Create a radial gradient, transparent in the middle.
      java.awt.geom.Point2D center = new java.awt.geom.Point2D.Float(mX, mY);
      float radius = 72;
      float[] dist = {0.0f, 1.0f};
      Color[] colors = {new Color(0.0f, 0.0f, 0.0f, 0.0f), Color.BLACK};
      RadialGradientPaint p =
          new RadialGradientPaint(center, radius, dist, colors);
      g2.setPaint(p);
      g2.setComposite(AlphaComposite.getInstance(
          AlphaComposite.SRC_OVER, .6f));
      g2.fillRect(0, 0, c.getWidth(), c.getHeight());
    }

    g2.dispose();
  }

  @Override
  protected void processMouseEvent(MouseEvent e, JLayer l) {
    if (e.getID() == MouseEvent.MOUSE_ENTERED) mActive = true;
    if (e.getID() == MouseEvent.MOUSE_EXITED) mActive = false;
    l.repaint();
  }

  @Override
  protected void processMouseMotionEvent(MouseEvent e, JLayer l) {
    Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l);
    mX = p.x;
    mY = p.y;
    l.repaint();
  }
}

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

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

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

跟随鼠标的聚光灯

Source code:

Java Web Start一起运行:

动画忙碌指示器

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

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

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

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

繁忙状态指示灯

Source code:

Java Web Start一起运行:

验证文本字段

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

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

立即反馈 Importing 错误

Source code:

Java Web Start一起运行: