Есть ли способ добиться безупречной графики в компонентах Swing / AWT?
Я реализую настраиваемую границу для кнопки, расширяя AbstractBorder
. Для демонстрационных целей толщина всегда равна 3, и каждая линия окрашивается в другой цвет.
import javax.swing.*;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicLookAndFeel;
import java.awt.*;
public class ExampleFrame extends JFrame {
public ExampleFrame() {
init();
}
public static void main(String[] args) throws UnsupportedLookAndFeelException
{
UIManager.setLookAndFeel(new CustomLookAndFeel());
ExampleFrame frame = new ExampleFrame();
frame.setVisible(true);
}
private void init() {
setLayout(new BorderLayout());
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
getContentPane().add(panel, BorderLayout.NORTH);
GridBagConstraints constraints = new GridBagConstraints();
JButton button1 = new JButton("aaa");
button1.setVerticalTextPosition(JButton.BOTTOM);
button1.setHorizontalTextPosition(JButton.CENTER);
button1.setFocusable(false);
constraints.insets = new Insets(2, 2, 2, 2);
constraints.gridx = 0;
constraints.gridy = 0;
constraints.gridwidth = 1;
constraints.gridheight = 3;
constraints.fill = GridBagConstraints.BOTH;
constraints.anchor = GridBagConstraints.LINE_START;
panel.add(button1, constraints);
JButton button2 = new JButton("bbb");
button2.setHorizontalAlignment(JButton.CENTER);
button2.setFocusable(false);
constraints.gridx = 1;
constraints.gridy = 0;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.fill = GridBagConstraints.BOTH;
constraints.anchor = GridBagConstraints.LINE_START;
panel.add(button2, constraints);
JButton button3 = new JButton("eee");
button3.setHorizontalAlignment(JButton.CENTER);
button3.setFocusable(false);
constraints.gridx = 1;
constraints.gridy = 1;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.anchor = GridBagConstraints.LINE_START;
panel.add(button3, constraints);
JButton button4 = new JButton("ddd");
button4.setHorizontalAlignment(JButton.CENTER);
button4.setFocusable(false);
constraints.gridx = 1;
constraints.gridy = 2;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.anchor = GridBagConstraints.LINE_START;
panel.add(button4, constraints);
pack();
}
private static final class CustomBorder extends AbstractBorder {
private final int thickness;
public CustomBorder() {
this(1);
}
public CustomBorder(int thickness) {
this.thickness = thickness;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
// ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
((Graphics2D) g).setStroke(new BasicStroke(1.0f));
for (int i = 0; i < thickness; i++) {
switch (i) {
case 0: {
g.setColor(Color.RED);
}
break;
case 1: {
g.setColor(Color.GREEN);
}
break;
case 2: {
g.setColor(Color.BLUE);
}
break;
}
// top-left -> top-right
g.drawLine(x, y + i, x + width, y + i);
// top-left > bottom-left
g.drawLine(x + i, y, x + i, y + height);
}
}
@Override
public Insets getBorderInsets(Component c, Insets insets) {
insets.top = thickness;
insets.bottom = thickness;
insets.left = thickness;
insets.right = thickness;
return insets;
}
}
private static final class CustomLookAndFeel extends BasicLookAndFeel {
@Override
protected void initComponentDefaults(UIDefaults table) {
super.initComponentDefaults(table);
// button
Border buttonBorder = new CustomBorder(3);
table.put("Button.border", buttonBorder);
table.put("Button.background", new Color(150, 150, 182));
table.put("Button.foreground", Color.BLACK);
}
@Override
public String getName() {
return "custom";
}
@Override
public String getID() {
return "custom";
}
@Override
public String getDescription() {
return "custom";
}
@Override
public boolean isNativeLookAndFeel() {
return false;
}
@Override
public boolean isSupportedLookAndFeel() {
return true;
}
}
}
Несмотря на то, что все 3 кнопки имеют одинаковые параметры границы, я получаю разные ширину границы и отображаемые цвета.
Подсказки VALUE_STROKE_PURE и VALUE_STROKE_NORMALIZE дают разные результаты, но ни один из них не является точным в пикселях.
Кнопки «bbb», «eee» и «ddd» на правой стороне имеют одинаковую ширину и высоту, но все же цвета и общая ширина границы различаются. Вдобавок используемый по умолчанию штрих имеет ширину 1.0f.
Я предполагаю, что это происходит, потому что Java 2D-геометрия работает с числами с плавающей запятой. Есть ли способ обойти это ограничение?
Я пробовал разные процедуры, например, drawRect и разные подсказки сглаживания, но безуспешно.