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

Java 11跨Temurin/Oracle JDK在多屏高DPI环境下实现窗口居中的代码验证与优化咨询

Java 11跨Temurin/Oracle JDK在多屏高DPI环境下实现窗口居中的代码验证与优化咨询

针对你遇到的Windows多屏高DPI环境下,Oracle JDK与Temurin(OpenJDK)窗口居中行为不一致的问题,我先帮你拆解下问题根源,再验证你的解决方案,并给出一些优化方向:

问题场景与根源分析

你的环境是主屏1920x1080(100%缩放)+ 左侧副屏4K(3840x2400,250%缩放),核心差异在于:

  • Temurin(OpenJDK):已经对高DPI多屏做了适配,GraphicsConfiguration.getBounds()返回的是逻辑坐标(即经过系统缩放系数转换后的坐标),所以你得到的x=-1536, width=1536正好是副屏的逻辑分辨率(3840/2.5=1536),居中计算自然正确。
  • Oracle JDK:在这个场景下getBounds()返回的是混合坐标(x是物理坐标,width/height是逻辑坐标),导致你计算的起始位置偏移,窗口跨屏显示。

你的解决方案验证

你通过判断Oracle VM,再用getDefaultTransform()的缩放系数修正Bounds的思路是可行的,因为:

  1. display.getDefaultTransform().getScaleX()/getScaleY()能准确获取当前屏幕的系统缩放系数(250%缩放时为2.5);
  2. 对Bounds的x/y/width/height做缩放转换后,能将Oracle JDK返回的混合坐标统一为和Temurin一致的逻辑坐标,确保居中计算逻辑统一。

代码优化建议

虽然你的代码能解决当前问题,但有几个可以提升鲁棒性的点:

  1. 避免硬编码判断Oracle VM
    依赖java.vendor判断厂商的方式不够灵活(比如未来Oracle JDK可能修复该问题,或者其他JDK也出现类似行为),建议改为基于行为判断:

    private static boolean needBoundsScaling(GraphicsConfiguration display, Rectangle bounds) {
        // 计算当前屏幕的逻辑宽度:物理宽度 / 缩放系数
        GraphicsDevice device = display.getDevice();
        int physicalWidth = device.getDisplayMode().getWidth();
        double scaleX = display.getDefaultTransform().getScaleX();
        int expectedLogicalWidth = (int) Math.round(physicalWidth / scaleX);
        // 如果当前Bounds的宽度和预期逻辑宽度不符,说明需要缩放修正
        return bounds.width != expectedLogicalWidth;
    }
    

    然后在updateBoundsForDisplayIfRequired里调用这个方法替代isOracleVM()。

  2. 统一坐标转换逻辑
    你当前的代码是直接用缩放系数乘以Bounds的各个属性,其实可以利用AffineTransform的transform方法直接转换整个Rectangle,更简洁且不易出错:

    private static void updateBoundsForDisplayIfRequired(Rectangle bounds, GraphicsConfiguration display) {
        AffineTransform transform = display.getDefaultTransform();
        // 因为transform是用户空间到设备空间,我们需要反向转换(设备到用户)
        try {
            transform = transform.createInverse();
        } catch (NoninvertibleTransformException e) {
            // 转换不可逆时直接返回,不做修改
            return;
        }
        // 转换Rectangle的四个顶点
        Point2D p1 = transform.transform(new Point2D.Double(bounds.x, bounds.y), null);
        Point2D p2 = transform.transform(new Point2D.Double(bounds.x + bounds.width, bounds.y + bounds.height), null);
        // 更新Bounds为转换后的逻辑坐标
        bounds.x = (int) Math.round(p1.getX());
        bounds.y = (int) Math.round(p1.getY());
        bounds.width = (int) Math.round(p2.getX() - p1.getX());
        bounds.height = (int) Math.round(p2.getY() - p1.getY());
    }
    

    这种方式能处理更复杂的变换(比如未来出现的旋转、拉伸等),而不仅仅是简单的缩放。

  3. 提前初始化窗口的GraphicsConfiguration
    确保窗口在调用center()方法前已经关联了正确的GraphicsConfiguration(比如在构造窗口时指定GraphicsConfiguration,而不是依赖默认值),避免因为窗口还未绑定到目标屏幕导致getGraphicsConfiguration()返回错误的配置。

总结

你的核心思路是正确的,通过统一坐标系统(将所有JDK的Bounds转换为逻辑坐标)来保证居中计算的一致性。优化后的代码能更好地适配未来的JDK版本和更多的屏幕配置场景。

备注:内容来源于stack exchange,提问作者Sergiy Medvynskyy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 20:19:51