如何用Java Swing实现叠加背景图的六边形JButton蜂窝表格
可行实现方案
这个需求完全可以通过自定义Swing组件和布局实现,以下是具体实现思路和核心代码:
1 自定义六边形JButton子类
默认JButton为矩形交互范围,需要重写核心方法实现六边形的绘制和点击判定:
- 用
Polygon类存储六边形六个顶点坐标 - 重写
paintComponent方法自定义六边形外观,支持常态、悬停、点击状态区分 - 重写
contains方法,保证只有点击在六边形范围内才会触发按钮交互
核心代码示例:
class HexButton extends JButton { private Polygon hexShape; private int sideLength = 30; // 可自定义六边形边长 public HexButton(String text) { super(text); setContentAreaFilled(false); // 关闭默认矩形填充 setBorderPainted(false); // 关闭默认矩形边框 setFocusPainted(false); initHexShape(); } private void initHexShape() { hexShape = new Polygon(); // 计算平顶六边形的六个顶点坐标 for (int i = 0; i < 6; i++) { double angle = Math.toRadians(60 * i - 30); int x = (int) (sideLength * Math.cos(angle)) + sideLength; int y = (int) (sideLength * Math.sin(angle)) + sideLength; hexShape.addPoint(x, y); } setPreferredSize(new Dimension(sideLength * 2, (int) (sideLength * Math.sqrt(3)))); } @Override protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 根据按钮状态绘制填充色 if (getModel().isPressed()) { g2d.setColor(Color.LIGHT_GRAY); } else if (getModel().isRollover()) { g2d.setColor(Color.CYAN); } else { g2d.setColor(getBackground()); } g2d.fill(hexShape); g2d.setColor(Color.BLACK); g2d.draw(hexShape); // 绘制按钮文字 FontMetrics fm = g2d.getFontMetrics(); int textX = (getWidth() - fm.stringWidth(getText())) / 2; int textY = (getHeight() + fm.getAscent() - fm.getDescent()) / 2; g2d.setColor(getForeground()); g2d.drawString(getText(), textX, textY); g2d.dispose(); super.paintComponent(g); } @Override public boolean contains(int x, int y) { return hexShape.contains(x, y); // 仅六边形范围内响应点击 } }
2 蜂窝网格布局实现
六边形错位排列的核心计算逻辑:
- 水平步长为
边长*1.5 - 垂直步长为
(边长*√3)/2 - 偶数行整体向右偏移半个六边形宽度,避免重叠
带背景的网格面板代码示例:
class HexGridPanel extends JPanel { private BufferedImage backgroundImg; private final int rows = 10; // 自定义网格行数 private final int cols = 10; // 自定义网格列数 private final int sideLength = 30; private final int hexWidth = sideLength * 2; private final int hexHeight = (int) (sideLength * Math.sqrt(3)); private final int horizontalStep = (int) (sideLength * 1.5); private final int verticalStep = hexHeight / 2; public HexGridPanel(BufferedImage background) { this.backgroundImg = background; setLayout(null); // 采用绝对定位实现自定义布局 initHexButtons(); } private void initHexButtons() { for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { HexButton btn = new HexButton(row + "," + col); // 偶数行向右偏移 int x = col * horizontalStep + (row % 2 == 0 ? 0 : sideLength); int y = row * verticalStep; btn.setBounds(x, y, hexWidth, hexHeight); add(btn); } } } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 先绘制背景图,自适应面板尺寸 if (backgroundImg != null) { g.drawImage(backgroundImg, 0, 0, getWidth(), getHeight(), this); } } }
3 优化建议
- 可以给
HexButton添加自定义属性存储地块资源、单位等数据,适配《文明》类游戏的地块逻辑 - 大网格(超过50*50)建议做可视区域裁剪绘制,避免Swing重绘卡顿
- 可通过渐变填充、纹理填充替换纯色填充,还原游戏的地块视觉效果
- 叠加鼠标事件监听器即可实现地块选中高亮、右键菜单等交互效果
内容的提问来源于stack exchange,提问作者Martin Serdült
相关产品推荐
相关产品推荐

