You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化Swing钢琴卷帘编辑器中数百个组件的绘制性能?

优化Swing批量组件重绘性能问题

开发钢琴卷帘音乐编辑器时,采用类似如下代码修改数百个矩形组件(音符)的背景色,运行速度极慢,能清晰看到单个方块逐个切换状态,求优化方案。

原问题代码

public class TestManyChildrenRepaint {

  public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> {
        
      // Container with many square panels in a grid
      final int NB_SQUARES = 70;
      JPanel squareContainer = new JPanel(new GridLayout(NB_SQUARES, NB_SQUARES, 1, 1));
      List<Square> squares = new ArrayList<>();
      for (int i=0; i < NB_SQUARES * NB_SQUARES; i++) {
        var s = new Square();
        squareContainer.add(s);
        squares.add(s);
      }


      // Toggle selection of all squares
      JButton toggle = new JButton("Toggle");
      toggle.addActionListener(e -> squares.forEach(s -> s.toggleSelection()));


      // Build JFrame
      JFrame frame = new JFrame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setLayout(new BorderLayout());
      frame.add(toggle, BorderLayout.WEST);
      frame.add(squareContainer, BorderLayout.EAST);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
    });
  }

  private static class Square extends JPanel {

    private boolean selected;

    public Square() {
      setPreferredSize(new Dimension(10, 10));
      setSelected(false);
    }

    public void toggleSelection() {
      setSelected(!selected);
    }

    public void setSelected(boolean b) {
      selected = b;
      setBackground(selected ? Color.BLUE : Color.ORANGE);
    }
  }
}

优化方案

1. 批量更新时禁用容器重绘,完成后统一重绘

每个setBackground()都会触发组件单独的repaint()请求,数百次零散重绘是卡顿的核心原因。可以在批量更新前让容器忽略重绘请求,更新完成后再触发一次整体重绘:

toggle.addActionListener(e -> {
    // 禁用容器重绘,避免零散刷新
    squareContainer.setIgnoreRepaint(true);
    // 批量更新所有方块状态
    squares.forEach(s -> s.toggleSelection());
    // 恢复重绘并触发整体刷新
    squareContainer.setIgnoreRepaint(false);
    squareContainer.repaint();
});

2. 替换大量JPanel为自定义绘制组件

用单个自定义组件替代数百个JPanel,直接在paintComponent()中绘制所有音符矩形,能大幅减少Swing组件树的开销,性能提升显著:

// 自定义绘制所有方块的容器
private static class SquareGrid extends JPanel {
    private final int nbSquares;
    private final boolean[][] selected;

    public SquareGrid(int nbSquares) {
        this.nbSquares = nbSquares;
        this.selected = new boolean[nbSquares][nbSquares];
        setPreferredSize(new Dimension(nbSquares * 11, nbSquares * 11)); // 包含1px间距
        setDoubleBuffered(true); // 开启双缓冲避免闪烁
    }

    public void toggleAll() {
        for (int row = 0; row < nbSquares; row++) {
            for (int col = 0; col < nbSquares; col++) {
                selected[row][col] = !selected[row][col];
            }
        }
        repaint(); // 仅触发一次重绘
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        // 关闭抗锯齿,小矩形场景下视觉收益低但开销大
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);

        int squareSize = 10;
        int gap = 1;
        for (int row = 0; row < nbSquares; row++) {
            for (int col = 0; col < nbSquares; col++) {
                int x = col * (squareSize + gap);
                int y = row * (squareSize + gap);
                g2d.setColor(selected[row][col] ? Color.BLUE : Color.ORANGE);
                g2d.fillRect(x, y, squareSize, squareSize);
            }
        }
    }
}

使用该组件时无需维护数百个JPanel,直接创建实例即可:

final int NB_SQUARES = 70;
SquareGrid squareGrid = new SquareGrid(NB_SQUARES);

JButton toggle = new JButton("Toggle");
toggle.addActionListener(e -> squareGrid.toggleAll());

// 替换原squareContainer添加到窗口
frame.add(squareGrid, BorderLayout.EAST);

3. 辅助优化点

  • 强制开启双缓冲:自定义组件显式调用setDoubleBuffered(true),避免绘制闪烁。
  • 简化渲染逻辑:关闭抗锯齿、避免复杂图形运算,优先保证绘制速度。
  • 移除布局依赖:自定义绘制直接通过坐标计算位置,减少GridLayout等布局系统的性能消耗。

内容的提问来源于stack exchange,提问作者jjazzboss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:10:55