Java布局比例:创建可伸缩的方形面板

我正在制作一个GUI组件来表示窗口中的棋盘。 通常它将是一个8×8正方形的网格,虽然有些变体需要10×8板等。第一步是制作一个包含8×8组件网格的面板。

Board扩展了JPanel并使用GridLayout来建模8×8组件的网格。 为了完成某些事情,这些只是扩展JButton Square类。 麻烦的是他们不是正方形!

Board已被添加到一个新实例化的JFrame ,在屏幕上打包和渲染。 当然,现在该板在用户resize时占用整个帧。 网格与板一起缩放,这会将方块扭曲成矩形。

这并非完全不受欢迎的行为。 我希望电路板能够与框架一起扩展。 但是,我想确保方块始终保持正方形。 电路板可以是矩形(10×8),但应保持固定比例。

我如何获得正方形?

您可以选择使用符合单元格首选大小的LayoutManagerGridLayout将为每个单元格提供相等数量的可用空间,这似乎不是您想要的。

例如, GridBagLayout东西

在此处输入图像描述

 public class TestChessBoard { public static void main(String[] args) { new TestChessBoard(); } public TestChessBoard() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ex) { } JFrame frame = new JFrame("Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new ChessBoardPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class ChessBoardPane extends JPanel { public ChessBoardPane() { int index = 0; setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { Color color = index % 2 == 0 ? Color.BLACK : Color.WHITE; gbc.gridx = col; gbc.gridy = row; add(new Cell(color), gbc); index++; } index++; } } } public class Cell extends JButton { public Cell(Color background) { setContentAreaFilled(false); setBorderPainted(false); setBackground(background); setOpaque(true); } @Override public Dimension getPreferredSize() { return new Dimension(25, 25); } } } 

更新了比例示例

现在,如果你想做一个比例布局(这样,无论可用空间如何,网格的每个单元格都保持与另一个单元格成比例),事情开始变得......有趣......

在此处输入图像描述

 public class TestChessBoard { public static void main(String[] args) { new TestChessBoard(); } public TestChessBoard() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ex) { } JFrame frame = new JFrame("Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new TestChessBoard.ChessBoardPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class ChessBoardPane extends JPanel { public ChessBoardPane() { int index = 0; setLayout(new ChessBoardLayoutManager()); for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { Color color = index % 2 == 0 ? Color.BLACK : Color.WHITE; add(new TestChessBoard.Cell(color), new Point(col, row)); index++; } index++; } } } public class Cell extends JButton { public Cell(Color background) { setContentAreaFilled(false); setBorderPainted(false); setBackground(background); setOpaque(true); } @Override public Dimension getPreferredSize() { return new Dimension(25, 25); } } public class ChessBoardLayoutManager implements LayoutManager2 { private Map mapComps; public ChessBoardLayoutManager() { mapComps = new HashMap<>(25); } @Override public void addLayoutComponent(Component comp, Object constraints) { if (constraints instanceof Point) { mapComps.put((Point) constraints, comp); } else { throw new IllegalArgumentException("ChessBoard constraints must be a Point"); } } @Override public Dimension maximumLayoutSize(Container target) { return preferredLayoutSize(target); } @Override public float getLayoutAlignmentX(Container target) { return 0.5f; } @Override public float getLayoutAlignmentY(Container target) { return 0.5f; } @Override public void invalidateLayout(Container target) { } @Override public void addLayoutComponent(String name, Component comp) { } @Override public void removeLayoutComponent(Component comp) { Point[] keys = mapComps.keySet().toArray(new Point[mapComps.size()]); for (Point p : keys) { if (mapComps.get(p).equals(comp)) { mapComps.remove(p); break; } } } @Override public Dimension preferredLayoutSize(Container parent) { return new CellGrid(mapComps).getPreferredSize(); } @Override public Dimension minimumLayoutSize(Container parent) { return preferredLayoutSize(parent); } @Override public void layoutContainer(Container parent) { int width = parent.getWidth(); int height = parent.getHeight(); int gridSize = Math.min(width, height); CellGrid grid = new CellGrid(mapComps); int rowCount = grid.getRowCount(); int columnCount = grid.getColumnCount(); int cellSize = gridSize / Math.max(rowCount, columnCount); int xOffset = (width - (cellSize * columnCount)) / 2; int yOffset = (height - (cellSize * rowCount)) / 2; Map> cellRows = grid.getCellRows(); for (Integer row : cellRows.keySet()) { List rows = cellRows.get(row); for (CellGrid.Cell cell : rows) { Point p = cell.getPoint(); Component comp = cell.getComponent(); int x = xOffset + (px * cellSize); int y = yOffset + (py * cellSize); comp.setLocation(x, y); comp.setSize(cellSize, cellSize); } } } public class CellGrid { private Dimension prefSize; private int cellWidth; private int cellHeight; private Map> mapRows; private Map> mapCols; public CellGrid(Map mapComps) { mapRows = new HashMap<>(25); mapCols = new HashMap<>(25); for (Point p : mapComps.keySet()) { int row = py; int col = px; List rows = mapRows.get(row); List cols = mapCols.get(col); if (rows == null) { rows = new ArrayList<>(25); mapRows.put(row, rows); } if (cols == null) { cols = new ArrayList<>(25); mapCols.put(col, cols); } Cell cell = new Cell(p, mapComps.get(p)); rows.add(cell); cols.add(cell); } int rowCount = mapRows.size(); int colCount = mapCols.size(); cellWidth = 0; cellHeight = 0; for (List comps : mapRows.values()) { for (Cell cell : comps) { Component comp = cell.getComponent(); cellWidth = Math.max(cellWidth, comp.getPreferredSize().width); cellHeight = Math.max(cellHeight, comp.getPreferredSize().height); } } int cellSize = Math.max(cellHeight, cellWidth); prefSize = new Dimension(cellSize * colCount, cellSize * rowCount); System.out.println(prefSize); } public int getRowCount() { return getCellRows().size(); } public int getColumnCount() { return getCellColumns().size(); } public Map> getCellColumns() { return mapCols; } public Map> getCellRows() { return mapRows; } public Dimension getPreferredSize() { return prefSize; } public int getCellHeight() { return cellHeight; } public int getCellWidth() { return cellWidth; } public class Cell { private Point point; private Component component; public Cell(Point p, Component comp) { this.point = p; this.component = comp; } public Point getPoint() { return point; } public Component getComponent() { return component; } } } } } 

这有点长,所以这里有一个快速的答案:如果用户的电路板尺寸(8×8,10×8),你不能用方形正方形保持方形板,如果用户可以调整尺寸,则完全填满屏幕。 您应该限制电路板的尺寸,以便它保持纵横比,即使这意味着您的框架中有一些空白区域。 好的,请继续阅读冗长的解释……

有两种方法可以使这项工作。 您可以限制JFrame的可能大小,也可以限制Board的大小,使其不会始终填充框架。 限制电路板的尺寸是更常用的方法,所以让我们从这开始。

选项1:限制董事会

如果您正在使用一组固定的电路板尺寸(8×8,10×8,以及其他几个电路板尺寸),并假设每个方块都有一些最小尺寸(棋盘上的1个像素方块听起来不太实用),那么只有如此多的框架尺寸,电路板可以完全填充。 如果您的框架是80像素,80像素,那么您的8×8电路板非常适合。 但是一旦用户调整到像85×80这样的东西你就会被卡住,因为你不能完全填充它,同时保持你给出的板尺寸方块。

在这种情况下,您希望将5个像素留空,无论是上面还是下面的5个,或者上面和下面的2.5,或者其他什么都无关紧要。 这应该听起来很熟悉 – 这是一个宽高比问题,基本上为什么你可以根据电视和电影尺寸在电视边缘上看到黑条。

选项2:限制框架

如果您希望电路板始终完全填满框架,可能不是您想要的,那么您必须在用户调整框架大小后调整框架的大小。 假设您使用的是10×8电路板,用户将帧设置为107×75。 这不是太糟糕,只需要一点点数学,你就能发现100×80是你最接近的宽高比,然后修复窗口。 如果窗口不停地跳到它们上面,对用户来说可能会有点令人沮丧,特别是如果他们试图让它像50×200那样。

最后的想法/例子

限制电路板很可能是正确的解决方案。 从游戏到桌面应用程序的一切都遵循这一原则。 以MS Office产品中的function区为例。 当您使窗口变大时,function区将扩展(保持其比例),直到达到最大尺寸,然后您只需为文档获得更多空间。 当你使窗口变小时,色带变小(再次保持其比例)直到达到最小尺寸,然后你开始丢失它的一部分(记住,不要在你的板上有1×1方格)。

另一方面,您可以完全阻止用户调整窗口大小。 我很确定这就是MineSweeper的工作原理(不要在这台计算机上进行仔细检查),并且可能是您需要的更好/更简单的解决方案。