PAYATHON 2026

如何调整 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 的点击行为。

备注:内容仅供参考。