Java Swing 125%系统缩放时ScrollPane图形绘制不一致求助
问题描述
- 100%屏幕缩放比例下,绘制的图形始终像素完美;
- 125%屏幕缩放时,同一图形有时顶部会多一行像素,在JScrollPane列表中上下滚动时,图形会在两种状态间切换,出现明显闪烁晃动;
- 推测问题与组件位置相关,是图形绘制在两个像素之间,导致系统随机选择上方或下方像素进行绘制。
测试代码
package debug; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; public class TestBugScaling implements Runnable { private final int size = 38; public static void main(String[] args) { SwingUtilities.invokeLater(new TestBugScaling()); } @Override public void run() { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setPreferredSize(new Dimension(400, 400)); Container contentPane = frame.getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.setPreferredSize(new Dimension(400, 400)); JScrollPane scrollPane = new JScrollPane(); scrollPane.setPreferredSize(new Dimension(size + 10, 400)); scrollPane.getVerticalScrollBar().setUnitIncrement(size + 5); scrollPane.getVerticalScrollBar().setBlockIncrement((size + 5) * 2); JPanel container = new JPanel(); container.setBorder(new EmptyBorder(5, 5, 5, 5)); container.setLayout(new GridLayout(20, 0, 0, 5)); container.setPreferredSize(new Dimension(size + 10, (size + 5) * 20)); for (int i = 0; i < 20; i++) { container.add(new CirclePanel()); } scrollPane.setViewportView(container); contentPane.add(scrollPane, BorderLayout.NORTH); frame.pack(); frame.setVisible(true); } public class CirclePanel extends JPanel { public CirclePanel() { super(); setPreferredSize(new Dimension(size, size)); setMinimumSize(new Dimension(size, size)); setMaximumSize(new Dimension(size, size)); } @Override public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g.create(); g2.fillOval(0, 0, getHeight(), getHeight()); g2.dispose(); } } }
运行该代码,100%缩放时圆形滚动流畅;125%缩放时,滚动过程中圆形会上下偏移1像素,出现晃动。请问是否有方法预防或修复该问题?其成因是什么?可以接受图形不完全完美,但无法忍受闪烁和状态不一致的情况。
问题成因
这是Swing在非整数倍高DPI缩放场景下的像素对齐精度问题:
- 当系统缩放比例为非整数(如125%=5/4)时,组件的实际屏幕坐标会变为非整数(例如38px的组件,缩放后对应47.5px);
- Swing的绘制系统处理非整数坐标时,会采用插值或舍入策略,但滚动时组件的位置计算会出现微小偏差,导致绘制时的像素对齐状态反复切换,最终表现为图形晃动、闪烁。
修复方案
以下方法可解决或缓解该问题,按推荐优先级排序:
1. 强制绘制坐标对齐像素边界
修改CirclePanel的paintComponent方法,先获取组件的实际缩放后位置,将绘制坐标调整为整数,避免跨像素绘制:
@Override public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g.create(); // 获取当前屏幕的缩放比例 double scale = getGraphicsConfiguration().getDefaultTransform().getScaleY(); // 计算组件在屏幕上的实际Y坐标(非整数) double screenY = getY() * scale; // 计算需要偏移的量,让绘制对齐到像素边界 double offsetY = screenY - Math.floor(screenY); // 平移绘图上下文,抵消偏移 g2.translate(0, -offsetY); g2.fillOval(0, 0, getHeight(), getHeight()); g2.dispose(); }
2. 使用缩放比例兼容的组件尺寸
将组件尺寸改为缩放比例的整数倍,避免缩放后出现非整数像素。例如125%缩放时,选择40px(40*1.25=50,为整数)替代原来的38px:
private final int size = 40; // 替换原38
该方法简单直接,但需调整组件尺寸,适合对尺寸要求不严格的场景。
3. 启用Swing高DPI感知(JDK9+)
在程序启动时添加系统属性,让Swing更好地适配高DPI缩放:
static { System.setProperty("sun.java2d.uiScale.enabled", "true"); }
配合像素对齐方法使用,可提升整体缩放兼容性。
4. 强制关闭Swing自动缩放(备选)
通过JVM参数强制Swing使用原生像素绘制,关闭高DPI缩放:
-Dsun.java2d.uiScale=1
此方法会导致高DPI屏幕上组件显示模糊,仅作为最后备选方案。
内容的提问来源于stack exchange,提问作者Fraŋkini
相关产品推荐
相关产品推荐

