Java 中文官方教程 2022 版(二十)(2)https://developer.aliyun.com/article/1486750
使用LayerUI
类
LayerUI
类大部分行为都继承自ComponentUI
类。以下是最常重写的方法:
- 当目标组件需要绘制时,会调用
paint(Graphics g, JComponent c)
方法。为了以与 Swing 相同的方式呈现组件,调用super.paint(g, c)
方法。 - 当你的
LayerUI
子类与一个组件关联时,会调用installUI(JComponent c)
方法。在这里执行任何必要的初始化。传入的组件是相应的JLayer
对象。使用JLayer
类的getView()
方法检索目标组件。 - 当你的
LayerUI
子类不再与给定组件关联时,会调用uninstallUI(JComponent c)
方法。如果需要,进行清理。
在组件上绘制
要使用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; } }
这是结果:
源代码:
Wallpaper 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; } // Only create the off-screen 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()
方法中,面板被渲染到一个离屏图像中。离屏图像使用卷积运算符进行处理,然后绘制到屏幕上。
整个用户界面仍然活跃,只是模糊了:
源代码:
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(); // 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()
方法显示了面板的默认外观,然后叠加了一个径向渐变以实现聚光灯效果:
源代码:
Diva NetBeans Project
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 运行:
如何使用动作
Action
可用于将功能和状态与组件分离。例如,如果有两个或更多执行相同功能的组件,请考虑使用Action
对象来实现该功能。Action
对象是一个 action listener,不仅提供动作事件处理,还提供对动作事件触发组件的状态的集中处理,例如工具栏按钮、菜单项、常用按钮和文本字段。动作可以处理的状态包括文本、图标、助记键、启用和选定状态。
通常使用setAction
方法将动作附加到组件。当在组件上调用setAction
时会发生什么:
- 组件的状态会更新以匹配
Action
的状态。例如,如果Action
的文本和图标值已设置,则组件的文本和图标将设置为这些值。 Action
对象在组件上注册为动作监听器。- 如果
Action
的状态发生变化,组件的状态将更新以匹配Action
。例如,如果更改动作的启用状态,则所有附加到它的组件将更改其启用状态以匹配动作。
这里有一个示例,创建一个工具栏按钮和菜单项,执行相同的功能:
Action leftAction = new LeftAction(); *//LeftAction code is shown later* ... button = new JButton(leftAction) ... menuItem = new JMenuItem(leftAction);
要创建一个Action
对象,通常创建AbstractAction
的子类,然后实例化它。在子类中,必须实现actionPerformed
方法,在动作事件发生时做出适当反应。这里有一个创建和实例化AbstractAction
子类的示例:
leftAction = new LeftAction("Go left", anIcon, "This is the left button.", new Integer(KeyEvent.VK_L)); ... class LeftAction extends AbstractAction { public LeftAction(String text, ImageIcon icon, String desc, Integer mnemonic) { super(text, icon); putValue(SHORT_DESCRIPTION, desc); putValue(MNEMONIC_KEY, mnemonic); } public void actionPerformed(ActionEvent e) { displayResult("Action for first button/menu item", e); } }
当前面的代码创建的动作附加到按钮和菜单项时,按钮和菜单项将显示与动作关联的文本和图标。按钮和菜单项上使用L
字符作为助记键,并且它们的工具提示文本设置为SHORT_DESCRIPTION
字符串,后跟助记键的表示。
例如,我们提供了一个简单的示例,ActionDemo.java
,定义了三个操作。每个操作都附加到一个按钮和一个菜单项。由于为每个按钮的操作设置了助记符值,按键序列Alt-L
激活左按钮,Alt-M
激活中间按钮,Alt-R
激活右按钮。左按钮的工具提示显示这是左按钮。Alt-L. 所有这些配置都是自动完成的,程序不需要显式调用设置助记符或工具提示文本。正如我们稍后将展示的,程序确实调用设置按钮文本,但只是为了避免使用操作已设置的值。
试试这个:
- 点击“启动”按钮以使用Java™ Web Start运行 ActionDemo(下载 JDK 7 或更高版本)。或者,要自行编译和运行示例,请参考示例索引。
- 从左侧菜单中选择顶部项目(菜单 > 向左)。
文本区域显示一些文本,标识事件源和接收事件的操作监听器。 - 点击工具栏中最左边的按钮。
文本区域再次显示有关事件的信息。请注意,尽管事件的来源不同,但两个事件都是由相同的操作监听器检测到的:附加到组件的Action
对象。 - 从操作状态菜单中选择顶部项目。
这将禁用“向左”Action
对象,进而禁用其关联的菜单项和按钮。
当“向左”操作被禁用时,用户看到的情况如下:
这是禁用“向左”操作的代码:
boolean selected = ...//*true if the action should be enabled;* //*false, otherwise* leftAction.setEnabled(selected);
在使用Action
创建组件后,您可能需要自定义它们。例如,您可能希望通过添加或删除图标或文本来自定义其中一个组件的外观。例如,ActionDemo.java
的菜单中没有图标,按钮中也没有文本。以下是实现此目的的代码:
menuItem = new JMenuItem(); menuItem.setAction(leftAction); menuItem.setIcon(null); //arbitrarily chose not to use icon in menu ... button = new JButton(); button.setAction(leftAction); button.setText(""); //an icon-only button
我们选择通过将图标属性设置为null
,将文本设置为空字符串,从同一个操作中创建一个仅图标的按钮和一个仅文本的菜单项。然而,如果Action
的属性发生变化,小部件可能会尝试再次从Action
中重置图标和文本。
Java 中文官方教程 2022 版(二十)(4)https://developer.aliyun.com/article/1486752