如何优化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
相关产品推荐
相关产品推荐

