带三角形边框的 JPanel

JPanel with triangular border

希望创建一个 JPanel 边框,如下图中以红色突出显示的那样......即一侧或两侧的三角形。我已经使用信息 here and here 成功实现了圆形 JPanel 边框。有什么想法或提示吗?

三角形的主要问题是两个组件需要相互重叠,而标准 Java 布局旨在防止重叠。

您有 3 个选项:

  1. 您需要 "fake" 重叠,方法是让一个组件在左侧绘制组件的三角形。
  2. 或者您可以创建一个位于两个矩形组件之间并呈现重叠的组件。
  3. 或者您需要使用以所需方式重叠它们的自定义布局管理器。

无论哪种方式,您都会遇到点击重叠区域只会转到其中一个按钮的问题,除非您添加特殊逻辑来适当地转发点击。

您或许可以使用 FlowLayout:

  • FlowLayout: 在水平间隙中设置为负值。
  • Override JRadioButton#contains(int, int)
  • Override JPanel#isOptimizedDrawingEnabled()

import java.awt.*;
import java.awt.geom.*;
import java.io.Serializable;
import java.util.*;
import java.util.List;
import javax.swing.*;

public final class FlowLayoutOverlapTest {
  private static final int BORDER = 1;
  public JComponent makeUI() {
    JPanel p = new JPanel(new GridLayout(0, 1));
    p.setBorder(BorderFactory.createEmptyBorder(20, 2, 20, 2));
    p.add(makeBreadcrumbList(0, Color.PINK,   Arrays.asList("overlap:", "0px", "button")));
    p.add(makeBreadcrumbList(5, Color.CYAN,   Arrays.asList("overlap:", "5px", "button")));
    p.add(makeBreadcrumbList(9, Color.ORANGE, Arrays.asList("overlap:", "9px", "button")));
    return p;
  }
  private static AbstractButton makeButton(String title, Color color) {
    final ToggleButtonBarCellIcon icon = new ToggleButtonBarCellIcon();
    AbstractButton b = new JRadioButton(title) {
      //http://java-swing-tips.blogspot.jp/2008/11/rounded-corner-jbutton.html
      @Override public boolean contains(int x, int y) {
        if (Objects.nonNull(icon) && Objects.nonNull(icon.area)) {
          return icon.area.contains(x, y);
        } else {
          return super.contains(x, y);
        }
      }
    };
    b.setIcon(icon);
    b.setContentAreaFilled(false);
    b.setBorder(BorderFactory.createEmptyBorder());
    b.setHorizontalTextPosition(SwingConstants.CENTER);
    b.setFocusPainted(false);
    b.setOpaque(false);
    b.setBackground(color);
    return b;
  }
  private static JPanel makePanel(int overlap) {
    //http://java-swing-tips.blogspot.com/2013/12/breadcrumb-navigation-with-jradiobutton.html
    JPanel p = new JPanel(new FlowLayout(FlowLayout.LEADING, -overlap, 0)) {
      @Override public boolean isOptimizedDrawingEnabled() {
        return false;
      }
    };
    p.setBorder(BorderFactory.createEmptyBorder(BORDER, overlap + BORDER, BORDER, BORDER));
    p.setOpaque(false);
    return p;
  }
  private static JComponent makeBreadcrumbList(int overlap, Color color, List<String> list) {
    JPanel p = makePanel(overlap + 1);
    ButtonGroup bg = new ButtonGroup();
    for (String title : list) {
      AbstractButton b = makeButton(title, color);
      p.add(b);
      bg.add(b);
    }
    return p;
  }
  public static void main(String... args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }
  public static void createAndShowGUI() {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(new FlowLayoutOverlapTest().makeUI());
    f.setSize(320, 240);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}

//http://java-swing-tips.blogspot.com/2012/11/make-togglebuttonbar-with-jradiobuttons.html
class ToggleButtonBarCellIcon implements Icon, Serializable {
  private static final long serialVersionUID = 1L;
  private static final int W = 10;
  private static final int H = 21;
  public Shape area;
  public Shape getShape(Container parent, Component c, int x, int y) {
    int w = c.getWidth() - 1;
    int h = c.getHeight() - 1;
    int h2 = (int)(h * .5 + .5);
    int w2 = W;
    Path2D.Float p = new Path2D.Float();
    p.moveTo(0,      0);
    p.lineTo(w - w2, 0);
    p.lineTo(w,      h2);
    p.lineTo(w - w2, h);
    p.lineTo(0,      h);
    if (c != parent.getComponent(0)) {
      p.lineTo(w2, h2);
    }
    p.closePath();
    return AffineTransform.getTranslateInstance(x, y).createTransformedShape(p);
  }
  @Override public void paintIcon(Component c, Graphics g, int x, int y) {
    Container parent = c.getParent();
    if (Objects.isNull(parent)) {
      return;
    }
    area = getShape(parent, c, x, y);

    Color bgc = parent.getBackground();
    Color borderColor = Color.GRAY.brighter();
    if (c instanceof AbstractButton) {
      ButtonModel m = ((AbstractButton) c).getModel();
      if (m.isSelected() || m.isRollover()) {
        bgc = c.getBackground();
        borderColor = Color.GRAY;
      }
    }
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setPaint(bgc);
    g2.fill(area);
    g2.setPaint(borderColor);
    g2.draw(area);
    g2.dispose();
  }
  @Override public int getIconWidth() {
    return 100;
  }
  @Override public int getIconHeight() {
    return H;
  }
}