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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 22:42:05