Linux下Java Swing窗口在副显示器上丢失透明度问题
临时解决跨显示器Swing窗口透明失效问题
我有一段Swing代码,主显示器上能正常渲染透明窗口,但把窗口手动或通过程序移到副显示器后,透明度直接丢失,背景变成黑色。这个问题在Redhat 8.2/8.4系统的OpenJDK 1.8/11/17版本里都能复现。已经给OpenJDK提交了Bug等官方修复,现在急需临时解决方案或者猴子补丁。
正常效果:
异常效果:
测试代码:
import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import javax.swing.JFrame; import javax.swing.JPanel; public class TransparencyTest { public static void main(String[] args) { JPanel panel = new Custom(); panel.setOpaque(false); JFrame frame = new JFrame(); frame.setUndecorated(true); frame.setBackground(new Color(0, 0, 0, 0)); frame.setTitle("Test"); frame.getContentPane().add(panel); frame.setSize(320, 240); frame.setResizable(false); frame.setMinimumSize(new Dimension(1, 1)); frame.setLocation(1920, 0); frame.setVisible(true); } private static class Custom extends JPanel { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); g.clearRect(0, 0, getWidth(), getHeight()); g.setColor(new Color(255, 0, 0, 127)); g.fillOval(0, 0, getWidth(), getHeight()); } } }
临时解决方案
1. 监听窗口移动,重置透明背景
窗口挪到新显示器时,强制重新设置透明背景并触发重绘,能快速修复黑色背景问题:
import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Point; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import javax.swing.JFrame; import javax.swing.JPanel; public class TransparencyTest { public static void main(String[] args) { JPanel panel = new Custom(); panel.setOpaque(false); JFrame frame = new JFrame(); frame.setUndecorated(true); frame.setBackground(new Color(0, 0, 0, 0)); frame.setTitle("Test"); frame.getContentPane().add(panel); frame.setSize(320, 240); frame.setResizable(false); frame.setMinimumSize(new Dimension(1, 1)); // 加个位置变化监听器 frame.addComponentListener(new ComponentAdapter() { private Point lastPos; @Override public void componentMoved(ComponentEvent e) { Point currentPos = frame.getLocation(); // 检测位置变化(如果是跨显示器移动,坐标会有明显跳转) if (lastPos != null && !currentPos.equals(lastPos)) { // 重置透明背景+强制重绘 frame.setBackground(new Color(0, 0, 0, 0)); frame.repaint(); } lastPos = currentPos; } }); frame.setLocation(1920, 0); frame.setVisible(true); } private static class Custom extends JPanel { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); g.clearRect(0, 0, getWidth(), getHeight()); g.setColor(new Color(255, 0, 0, 127)); g.fillOval(0, 0, getWidth(), getHeight()); } } }
2. 直接操作X11窗口属性(针对Redhat的Xorg环境)
绕开AWT的渲染问题,直接给X11窗口设置透明度属性,这个方法依赖OpenJDK内部API,仅作临时补丁用:
import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Window; import java.lang.reflect.Method; import javax.swing.JFrame; import javax.swing.JPanel; public class TransparencyTest { public static void main(String[] args) { JPanel panel = new Custom(); panel.setOpaque(false); JFrame frame = new JFrame(); frame.setUndecorated(true); frame.setTitle("Test"); frame.getContentPane().add(panel); frame.setSize(320, 240); frame.setResizable(false); frame.setMinimumSize(new Dimension(1, 1)); frame.setLocation(1920, 0); frame.setVisible(true); // 设置X11窗口全局透明度 setX11WindowOpacity(frame, 1.0f); // 保留AWT背景透明设置 frame.setBackground(new Color(0, 0, 0, 0)); } private static void setX11WindowOpacity(Window window, float opacity) { try { // 反射获取X11窗口实例 Method getPeer = Window.class.getDeclaredMethod("getPeer"); getPeer.setAccessible(true); Object peer = getPeer.invoke(window); Method getWindow = peer.getClass().getMethod("getWindow"); long x11Window = (Long) getWindow.invoke(peer); // 加载X11库并设置透明度属性 System.loadLibrary("X11"); Class<?> xlib = Class.forName("sun.awt.X11.XlibWrapper"); Method xChangeProp = xlib.getDeclaredMethod( "XChangeProperty", long.class, long.class, long.class, int.class, int.class, byte[].class ); // 获取_NET_WM_WINDOW_OPACITY的原子值 long opacityAtom = getAtom("_NET_WM_WINDOW_OPACITY"); // 把透明度转成X11需要的32位大端字节数组 int opacityInt = Float.floatToIntBits(opacity); byte[] opacityBytes = new byte[]{ (byte)(opacityInt >> 24), (byte)(opacityInt >> 16), (byte)(opacityInt >> 8), (byte)opacityInt }; xChangeProp.invoke(null, x11Window, opacityAtom, 6 /*XA_CARDINAL*/, 32, 0 /*PropModeReplace*/, opacityBytes); } catch (Exception e) { e.printStackTrace(); } } private static long getAtom(String atomName) throws Exception { Class<?> xlib = Class.forName("sun.awt.X11.XlibWrapper"); Method xInternAtom = xlib.getDeclaredMethod("XInternAtom", long.class, String.class, boolean.class); long display = (Long) xlib.getDeclaredMethod("getDefaultDisplay").invoke(null); return (Long) xInternAtom.invoke(null, display, atomName, false); } private static class Custom extends JPanel { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); g.clearRect(0, 0, getWidth(), getHeight()); g.setColor(new Color(255, 0, 0, 127)); g.fillOval(0, 0, getWidth(), getHeight()); } } }
3. 启用OpenGL渲染加速
试试强制Swing用OpenGL渲染,可能解决跨显示器的渲染不一致问题:
- 启动程序时加参数:
-Dsun.java2d.opengl=true - 或者在代码开头设置:
System.setProperty("sun.java2d.opengl", "true");
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

