文档

Java™ 教程
隐藏目录
使用 JLayer 类装饰组件
路径: 使用Swing创建GUI
课程: 使用其他Swing功能

如何使用JLayer类装饰组件

JLayer类是Swing组件的一个灵活而强大的装饰器。它使您能够在组件上绘制并响应组件事件,而无需直接修改底层组件。

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

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

观看视频需要启用JavaScript的Web浏览器和Internet连接。如果您无法看到视频,请尝试在YouTube上观看

使用JLayer

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

例如,要将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类继承了大部分行为。下面是最常被重写的方法:

在组件上绘制

要使用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("壁纸");
    
    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("牛肉", true));
    entreeGroup.add(radioButton);
    p.add(radioButton = new JRadioButton("鸡肉"));
    entreeGroup.add(radioButton);
    p.add(radioButton = new JRadioButton("蔬菜"));
    entreeGroup.add(radioButton);

    p.add(new JCheckBox("番茄酱"));
    p.add(new JCheckBox("芥末"));
    p.add(new JCheckBox("泡菜"));

    p.add(new JLabel("特殊要求:"));
    p.add(new JTextField(20));

    JButton orderButton = new JButton("下单");
    p.add(orderButton);

    return p;
  }
}

这是结果:

一个带有花哨装饰的面板

源代码:

壁纸 NetBeans 项目
Wallpaper.java

使用 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;
    }

    // 如果现有的离屏图像大小不对,则创建一个新的离屏图像。
    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()方法中,面板被渲染到一个离屏图像中。离屏图像经过卷积运算处理,然后绘制到屏幕上。

整个用户界面仍然可以交互,只是模糊的:

图形反转的用户界面

源代码:

Myopia NetBeans 项目
Myopia.java

使用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();

    // 绘制视图。
    super.paint (g2, c);

    if (mActive) {
      // 创建一个径向渐变,中间透明。
      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()方法显示面板的默认外观,然后叠加径向渐变以实现聚光灯效果:

跟随鼠标的聚光灯

源代码:

Diva NetBeans项目
Diva.java

使用Java Web Start运行:

启动示例

动画忙指示器

此示例是一个动画忙指示器。它演示了在LayerUI子类中的动画效果,并具有淡入和淡出的特点。它比之前的示例更复杂,但基于相同的原则,即定义一个paint()方法进行自定义绘图。

点击下订单按钮,可以看到4秒钟的忙指示器。请注意面板被变灰,并且指示器在旋转。指示器的元素具有不同的透明度。

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

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

平滑的忙指示器

源代码:

TapTapTap NetBeans 项目
TapTapTap.java

使用Java Web Start运行:

启动示例

验证文本字段

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

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

针对错误输入的即时反馈

源代码:

FieldValidator NetBeans项目
FieldValidator.java

使用Java Web Start运行:

启动示例

上一页: 如何创建透明和自定义形状的窗口
下一页: 如何使用Actions