如何调整 Java Swing 背包界面的布局、滚动方向和按钮大小?
将物品按钮按 BoxLayout.Y_AXIS 纵向排列,再限制按钮高度,就能解决按钮排成一行和被拉大的问题。滚动条设为垂直按需显示、水平不显示。如果还需要按钮始终适应可视区域的宽度,可以让内容面板实现 Scrollable。
VERTICAL_SCROLLBAR_ALWAYS 控制垂直滚动条是否一直显示,物品的排列方向则由布局管理器决定。
为什么出现水平滚动条和大按钮?
使用无参构造:
new GridLayout()
默认只排一行,列数随组件数量增加。添加多个按钮后,它们会横向排列,面板所需的宽度也随之增加,因此出现水平滚动条。
GridLayout 还会把可用空间平均分给每个组件。按钮较少时,每个按钮分到的区域就会比较大。即使调用 setSize(),布局管理器也可能在下次布局时重新设置按钮尺寸。
JScrollPane 根据内容尺寸提供滚动功能。按钮如何排列、能拉伸到多大,需要在内容面板中设置。
设置纵向布局和滚动策略
要让按钮宽度随背包区域变化,可以让内容面板实现 Scrollable:宽度跟随视口,高度随物品数量增长。
代码需要 javax.swing.* 和 java.awt.* 中的相关类。
class InventoryPanel extends JPanel implements Scrollable {
InventoryPanel() {
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
@Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
@Override
public int getScrollableUnitIncrement(
Rectangle visibleRect, int orientation, int direction) {
return 24; // 示例:每次小幅滚动 24 像素
}
@Override
public int getScrollableBlockIncrement(
Rectangle visibleRect, int orientation, int direction) {
int extent = orientation == SwingConstants.VERTICAL
? visibleRect.height : visibleRect.width;
return Math.max(1, extent - 24);
}
}
接着修改 GameUI 中的初始化方法:
public void createInventoryArea() {
listPanel = new InventoryPanel();
listPanel.setBackground(Color.BLACK);
inventoryScroll = new JScrollPane(
listPanel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
);
inventoryScroll.setBounds(550, 350, 300, 200);
inventoryScroll.setBorder(null);
inventoryScroll.getViewport().setBackground(Color.BLACK);
inventoryScroll.setVisible(false);
con.add(inventoryScroll);
}
使用 VERTICAL_SCROLLBAR_AS_NEEDED 时,物品超出可视区域后才会显示垂直滚动条。如果希望滚动条一直显示,将它换成:
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS
HORIZONTAL_SCROLLBAR_NEVER 只会隐藏水平滚动条,过宽的内容仍可能被裁切。让 getScrollableTracksViewportWidth() 返回 true,才能使内容面板的宽度跟随视口变化。
限制按钮高度,让宽度适应面板
BoxLayout 会考虑组件的最小、首选和最大尺寸。背包列表可以保留按钮根据字体计算出的首选高度,让宽度随面板伸缩:
public void makeInventory() {
ui.listPanel.removeAll();
InventoryHandler iHandler = new InventoryHandler();
for (Item item : player.getInventory()) {
JButton inventoryButton = new JButton(item.getName());
inventoryButton.setBackground(Color.BLACK);
inventoryButton.setForeground(Color.WHITE);
inventoryButton.setFont(ui.normalFont);
inventoryButton.setFocusPainted(false);
Dimension preferred = inventoryButton.getPreferredSize();
inventoryButton.setMinimumSize(
new Dimension(0, preferred.height)
);
inventoryButton.setMaximumSize(
new Dimension(Integer.MAX_VALUE, preferred.height)
);
inventoryButton.setAlignmentX(Component.LEFT_ALIGNMENT);
inventoryButton.addActionListener(iHandler);
inventoryButton.setActionCommand("item");
ui.listPanel.add(inventoryButton);
}
ui.listPanel.revalidate();
ui.listPanel.repaint();
}
按钮可以横向填满面板,高度也不会因为物品少而被拉大。统一使用 LEFT_ALIGNMENT,还能避免按钮因对齐方式不同而错位。
物品名过长时,单行按钮可能无法显示完整文字。可以添加提示,让鼠标悬停时显示完整名称:
inventoryButton.setToolTipText(item.getName());
使用时还要注意什么?
setBounds()适用于父容器采用绝对定位的情况,例如con使用null布局。如果父容器有布局管理器,应由它安排inventoryScroll的位置和尺寸。- 初始化时调用了
setVisible(false),打开背包时需要调用inventoryScroll.setVisible(true)。 - Swing 组件应在事件派发线程(EDT)中创建和更新。按钮事件回调通常已在 EDT 中执行;如果游戏后台线程触发刷新,应通过
SwingUtilities.invokeLater(...)调度,同时避免修改正在遍历的背包集合。 - 所有按钮的
actionCommand都是"item",处理器无法仅凭这个值区分物品。可以通过putClientProperty("item", item)保存对应对象,再从事件源按钮中读取。
目前可以继续由 Game 调用界面刷新方法。后续整理代码时,可以将创建按钮的逻辑移入 GameUI,由它提供刷新背包的方法,减少 Game 对界面字段的直接访问。
物品多时是否适合换成 JList?
如果操作方式主要是先选择物品,再点击“使用”“丢弃”等按钮,JList<Item> 配合 ListModel 和独立操作按钮通常更方便,也能减少为每件物品创建实际按钮的开销。
如果每件物品都需要一个可直接点击的按钮,继续使用 JPanel、BoxLayout 和 JScrollPane 即可。JList 的单元格渲染器只负责绘制外观,即使把单元格画成按钮,也不会自动获得普通 JButton 的点击行为。
备注:内容仅供参考。
