如何使用 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仍处于活动状态,只是模糊:
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()
方法中,鼠标移动的坐标存储在mX
和mY
成员变量中,以便以后可以在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。这是一个例子:
Source code:
与Java Web Start一起运行: