我有一个 JButton,其中我重写了
paintComponent(Graphics)
函数
与一个孩子 JLabel (我意识到这听起来很愚蠢,我保证不是)
我有 mouseEntered(MouseEvent)
和 mouseExited(MouseEvent)
函数,可以更改标签的可见性,并设置布尔值告诉 paintComponent
在按钮上绘制半透明覆盖层
预期的行为是
JLabel
绘制在按钮覆盖层上。如果没有覆盖(覆盖paintComponent
),这可以完美地工作。
(我假设这不仅限于按钮,尽管我还没有测试过这个理论)
按钮类:
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class HoverButton extends JButton {
private final JLabel label;
private final String title;
private boolean entered = false;
public HoverButton(String title) {
label = new JLabel(title);
this.title = title;
int startChar = title.indexOf(']') + 1;
String regex = new StringBuilder("\\[[a-zA-Z0-9]+\\]").append("| \\[[A-Za-z0-9]+ [A-Za-z0-9]+\\]")
.append("| \\(decen\\)").append("| \\(eng, decen\\)").append("| \\(eng\\)")
.append("|\\{.+\\}").toString();
String text = String.format("<html><p><b>%s</b></p></html>",
title.substring(startChar).replaceAll(regex, "").trim());
label.setVisible(false);
add(label);
addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
super.mouseEntered(e);
entered = true;
label.setVisible(true);
}
@Override
public void mouseExited(MouseEvent e) {
super.mouseExited(e);
entered = false;
label.setVisible(false);
}
});
}
public String getTitle() {
return title;
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (g instanceof Graphics2D g2d) {
getIcon().paintIcon(this, g, getInsets().left, getInsets().top);
int xMax = getWidth() - getInsets().right - getInsets().left;
int yMax = getHeight() - getInsets().top - getInsets().bottom;
if (entered) {
g2d.setColor(new Color(0x88000000, true));
g2d.fillRect(getInsets().left, getInsets().top, xMax, yMax);
}
g2d.dispose();
}
}
@Override
public void setPreferredSize(Dimension d) {
super.setPreferredSize(new Dimension((int) (d.getWidth() + getInsets().right + getInsets().left),
(int) (d.getHeight() + getInsets().top + getInsets().bottom)));
label.setMinimumSize(d);
}
}
底部的
setPreferredSize(Dimension)
可以确保标签不会调整按钮的大小
button.setIcon(icon);
button.setPreferredSize(new Dimension(icon.getWidth(), icon.getHeight()));
应该在调用类中
您可以使用
JLayer
进行此类叠加绘画,其中事件将穿过标签。
JLayer
有两个组成部分:
JLayer
包裹的组件。 JLayer
会将其事件转发到视图。例如,在本例中,我们希望将事件传递给按钮。JPanel
,您可以像其他任何东西一样使用它。事件将通过它及其后代。例如,在这种情况下,我们可以将标签添加到玻璃窗格(JLabel
s 可以接受 Icon
和文本,或 Icon
本身)。这样您就不必执行以下操作:
ButtonModel
)。Icon
。该标签将为您做到这一点。JLayer
会处理这个问题。import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.BorderFactory;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayer;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class MainWithJLayer {
/**
* Changes the alpha component of the given {@code Color}.
* @param c
* @param alpha
* @return
*/
public static Color withAlpha(final Color c,
final int alpha) {
return new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
}
/** A {@code JPanel} which always draws its background color (dishonoring opaque property). */
private static class AlwaysDrawBackgroundPanel extends JPanel {
@Override
protected void paintComponent(final Graphics g) {
final Color originalColor = g.getColor();
try {
final Rectangle clipBounds = g.getClipBounds();
g.setColor(getBackground());
g.fillRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
}
finally {
g.setColor(originalColor);
super.paintComponent(g);
}
}
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
// try {
// UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// }
// catch (final ClassNotFoundException | IllegalAccessException | InstantiationException | UnsupportedLookAndFeelException exception) {
// System.err.println("Failed to set system L&F.");
// }
final JButton button = new JButton("always clickable... ...always clickable");
button.addActionListener(e -> System.out.println("Clicked!"));
final JLabel label = new JLabel("Label overlay!", JLabel.CENTER);
label.setForeground(Color.RED);
final JPanel glassPane = new AlwaysDrawBackgroundPanel();
glassPane.setLayout(new BorderLayout());
glassPane.setBackground(withAlpha(Color.BLACK, 0x88)); //new Color(0x88000000,true)
glassPane.add(label, BorderLayout.CENTER);
final JLayer<JButton> layer = new JLayer<>(button);
layer.setGlassPane(glassPane);
glassPane.setOpaque(false); //This is mandatory in order to show the button under the label.
final JPanel contents = new JPanel(new BorderLayout());
contents.setBorder(BorderFactory.createEmptyBorder(100, 100, 100, 100));
contents.add(layer, BorderLayout.CENTER);
//Change glass pane visibility when hovering the button:
final ButtonModel buttonModel = button.getModel();
buttonModel.addChangeListener(e -> glassPane.setVisible(buttonModel.isRollover()));
final JFrame frame = new JFrame("Button overlay label");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(contents);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
请注意,只有当我们将鼠标悬停在按钮上时,玻璃窗格(带有标签)才可见,并且按钮正常接收事件(无论标签是否可见)。
按钮和标签还有多个属性(例如边距、边框、对齐方式、文本位置、图标-文本-间隙)来帮助解决两个组件之间的图标(和/或文本)放置问题。