这些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运行: