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的思路是可行的,因为:
display.getDefaultTransform().getScaleX()/getScaleY()能准确获取当前屏幕的系统缩放系数(250%缩放时为2.5);- 对Bounds的x/y/width/height做缩放转换后,能将Oracle JDK返回的混合坐标统一为和Temurin一致的逻辑坐标,确保居中计算逻辑统一。
代码优化建议
虽然你的代码能解决当前问题,但有几个可以提升鲁棒性的点:
避免硬编码判断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()。统一坐标转换逻辑
你当前的代码是直接用缩放系数乘以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()); }这种方式能处理更复杂的变换(比如未来出现的旋转、拉伸等),而不仅仅是简单的缩放。
提前初始化窗口的GraphicsConfiguration
确保窗口在调用center()方法前已经关联了正确的GraphicsConfiguration(比如在构造窗口时指定GraphicsConfiguration,而不是依赖默认值),避免因为窗口还未绑定到目标屏幕导致getGraphicsConfiguration()返回错误的配置。
总结
你的核心思路是正确的,通过统一坐标系统(将所有JDK的Bounds转换为逻辑坐标)来保证居中计算的一致性。优化后的代码能更好地适配未来的JDK版本和更多的屏幕配置场景。
备注:内容来源于stack exchange,提问作者Sergiy Medvynskyy

