这些Java教程是针对JDK 8编写的。本页中描述的示例和实践不利用后续版本中引入的改进,并可能使用不再可用的技术。
有关Java SE 9及其后续版本中更新的语言特性的摘要,请参阅Java语言更改。
有关所有JDK版本的新功能、增强功能以及已删除或已弃用选项的信息,请参阅JDK发布说明。
JLayer类是Swing组件的一个灵活而强大的装饰器。它使您能够在组件上绘制并响应组件事件,而无需直接修改底层组件。
本文档描述了展示JLayer类功能的示例。完整的源代码可供使用。
要对此页面上的材料进行简要介绍,请观看以下视频。
观看视频需要启用JavaScript的Web浏览器和Internet连接。如果您无法看到视频,请尝试在YouTube上观看。
JLayer类javax.swing.JLayer类是一个团队的一半。另一半是javax.swing.plaf.LayerUI类。假设您想要在JButton对象上进行一些自定义绘制(装饰 JButton对象)。您要装饰的组件是目标。
LayerUI子类的实例来进行绘制。LayerUI对象的JLayer对象。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)方法。installUI(JComponent c)方法在你的LayerUI子类的实例与组件关联时调用。在这里执行任何必要的初始化。传入的组件是相应的JLayer对象。使用JLayer类的getView()方法获取目标组件。uninstallUI(JComponent c)方法在你的LayerUI子类的实例不再与给定组件关联时调用。在这里进行必要的清理。要使用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;
}
}
这是结果:
源代码:
使用 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()方法中,面板被渲染到一个离屏图像中。离屏图像经过卷积运算处理,然后绘制到屏幕上。
整个用户界面仍然可以交互,只是模糊的:
源代码:
使用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()方法中,将鼠标移动的坐标存储在mX和mY成员变量中,以便稍后在paint()方法中使用。
paint()方法显示面板的默认外观,然后叠加径向渐变以实现聚光灯效果:
源代码:
使用Java Web Start运行:
此示例是一个动画忙指示器。它演示了在LayerUI子类中的动画效果,并具有淡入和淡出的特点。它比之前的示例更复杂,但基于相同的原则,即定义一个paint()方法进行自定义绘图。
点击下订单按钮,可以看到4秒钟的忙指示器。请注意面板被变灰,并且指示器在旋转。指示器的元素具有不同的透明度。
LayerUI子类WaitLayerUI展示了如何触发属性更改事件以更新组件。WaitLayerUI类使用一个Timer对象,以每秒24次的频率更新其状态。这发生在计时器的目标方法actionPerformed()中。
actionPerformed()方法使用firePropertyChange()方法来指示内部状态已更新。这将触发对applyPropertyChange()方法的调用,该方法重新绘制JLayer对象:
源代码:
使用Java Web Start运行:
本文档中的最后一个示例展示了如何使用JLayer类来装饰文本字段以显示其是否包含有效数据。虽然其他示例使用JLayer类来包装面板或一般组件,但此示例演示了如何专门包装JFormattedTextField组件。它还演示了单个LayerUI子类实现可用于多个JLayer实例的方法。
JLayer类用于为具有无效数据的字段提供可视化指示。当ValidationLayerUI类绘制文本字段时,如果字段内容无法解析,它会绘制一个红色的X。下面是一个示例:
源代码:
使用Java Web Start运行: