Java SE 6 Update N(通稱 Consumer JRE)在 com.sun.awt.AWTUtilities 中引入三大核心 API,讓 Swing/AWT 終於原生支援:
setWindowOpacity(Window, float):整體不透明度(0.0f~1.0f)setWindowShape(Window, Shape):非矩形視窗剪裁setWindowOpaque(Window, boolean):啟用逐像素透明度(per-pixel translucency)⚠️ 注意:此類位於 com.sun.* 非標準包,Java 7+ 可能遷移至 java.awt,升級時需適配。
| 方法 | 參數 | 效果 | 備註 |
|---|---|---|---|
| setWindowOpacity | Window, float (0~1) | 整體半透明 | 不支援逐像素 Alpha |
| setWindowShape | Window, Shape | 任意幾何形狀剪裁 | 邊緣易鋸齒,需配合 setWindowOpaque(false) 抗鋸齒 |
| setWindowOpaque | Window, boolean | true=不透明(預設), false=支援逐像素 Alpha | 必須搭配自訂繪製背景 |
public class TranslucentPopupFactory extends PopupFactory {
@Override
public Popup getPopup(Component owner, Component contents, int x, int y) {
return new TranslucentPopup(owner, contents, x, y);
}
}
// 安裝:PopupFactory.setSharedInstance(new TranslucentPopupFactory());
class TranslucentPopup extends Popup {
private final JWindow popupWindow;
private final boolean toFade;
private int currOpacity = 0;
private Timer fadeInTimer, fadeOutTimer;
TranslucentPopup(Component owner, Component contents, int x, int y) {
popupWindow = new JWindow();
// ToolTip 0.8f,其他 0.95f
float opacity = (contents instanceof JToolTip) ? 0.8f : 0.95f;
AWTUtilities.setWindowOpacity(popupWindow, opacity);
popupWindow.setLocation(x, y);
popupWindow.getContentPane().add(contents, BorderLayout.CENTER);
// 陰影邊框需逐像素透明
AWTUtilities.setWindowOpaque(popupWindow, false);
((JComponent)contents.getParent()).setBorder(new ShadowPopupBorder());
toFade = contents instanceof JToolTip;
}
@Override public void show() {
popupWindow.setVisible(true);
popupWindow.pack();
if (toFade) fadeIn();
}
private void fadeIn() {
AWTUtilities.setWindowOpacity(popupWindow, 0f);
fadeInTimer = new Timer(50, e -> {
currOpacity += 20;
if (currOpacity <= 100) {
AWTUtilities.setWindowOpacity(popupWindow, currOpacity / 100f);
popupWindow.getContentPane().repaint(); // 修正 6670649
} else {
fadeInTimer.stop();
}
});
fadeInTimer.start();
}
@Override public void hide() {
if (toFade) fadeOut();
else disposeWindow();
}
private void fadeOut() {
if (fadeInTimer != null && fadeInTimer.isRunning()) fadeInTimer.stop();
fadeOutTimer = new Timer(50, e -> {
currOpacity -= 10;
if (currOpacity >= 0) {
AWTUtilities.setWindowOpacity(popupWindow, currOpacity / 100f);
popupWindow.getContentPane().repaint();
} else {
fadeOutTimer.stop();
disposeWindow();
}
});
fadeOutTimer.start();
}
private void disposeWindow() {
popupWindow.setVisible(false);
popupWindow.removeAll();
popupWindow.dispose();
}
}
結合 RepaintManager、ComponentListener、WindowListener 同步主視窗與反射視窗:
public class JReflectionFrame extends JFrame {
private final JWindow reflection;
private final JPanel reflectionPanel;
public JReflectionFrame(String title) {
super(title);
reflection = new JWindow();
reflectionPanel = new JPanel() {
@Override protected void paintComponent(Graphics g) {
paintReflection(g); // 繪製鏡像+漸變遮罩
}
};
reflectionPanel.setDoubleBuffered(false); // 關鍵:雙緩衝會阻擋逐像素透明
reflectionPanel.setOpaque(false);
reflection.add(reflectionPanel);
reflection.setSize(getSize());
reflection.setLocation(getX(), getY() + getHeight());
reflection.setVisible(true);
// 同步位置/大小/可見性
addComponentListener(new ComponentAdapter() {
@Override public void componentMoved(ComponentEvent e) { syncReflection(); }
@Override public void componentResized(ComponentEvent e) { syncReflection(); }
@Override public void componentShown(ComponentEvent e) {
reflection.setVisible(true);
if (AWTUtilities.isWindowOpaque(reflection))
AWTUtilities.setWindowOpaque(reflection, false);
}
@Override public void componentHidden(ComponentEvent e) { reflection.setVisible(false); }
});
addWindowListener(new WindowAdapter() {
@Override public void windowActivated(WindowEvent e) {
reflection.setAlwaysOnTop(true);
reflection.setAlwaysOnTop(false); // 強制置頂閃爍技巧
}
});
// 強制重繪同步
RepaintManager.setCurrentManager(new RepaintManager() {
@Override public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
Window win = SwingUtilities.getWindowAncestor(c);
if (win instanceof JReflectionFrame) {
JRootPane rp = ((JReflectionFrame)win).getRootPane();
super.addDirtyRegion(rp, 0, 0, rp.getWidth(), rp.getHeight());
reflectionPanel.repaint();
} else super.addDirtyRegion(c, x, y, w, h);
}
});
}
private void syncReflection() {
reflection.setSize(getSize());
reflection.setLocation(getX(), getY() + getHeight());
}
// paintReflection 參考 Romain Guy 教學:離屏緩衝 -> 翻轉 -> Alpha 遮罩漸變
}
| 問題 | 原因 | 解決 |
|---|---|---|
| 非矩形視窗邊緣鋸齒嚴重 | Shape 裁剪無抗鋸齒 | setWindowOpaque(false) + 自訂背景繪製時開啟 Graphics2D.RENDERING_HINT_ANTIALIASING |
| 逐像素透明視窗背景全黑 | 未關閉雙緩衝或未設 opaque=false | JPanel.setDoubleBuffered(false); setOpaque(false); AWTUtilities.setWindowOpaque(window, false) |
| ToolTip 動畫閃爍 | repaint() 不觸發重繪 | 針對 Bug 6670649:呼叫 contentPane.repaint() 而非 window.repaint() |
| Linux/X11 無效 | 視窗管理器不支援 _NET_WM_WINDOW_TYPE_DOCK | 需啟用 Compositing Manager (Compiz, Mutter, KWin) 並確認 AWT Toolkit 為 XToolkit |
Java 7 將功能移至 java.awt.Window 實例方法:
// Java 7+
window.setOpacity(0.7f); // 替代 setWindowOpacity
window.setShape(new Ellipse2D.Double(...)); // 替代 setWindowShape
window.setBackground(new Color(0,0,0,0)); // 替代 setWindowOpaque(false) + 透明背景色
// 判斷支援:GraphicsDevice.isWindowTranslucencySupported(TRANSLUCENT/PERPIXEL_TRANSPARENT/UNIFORM_TRANSLUCENCY)
AWTUtilities 雖為非標準 API,卻在 Java 7 標準化前填補了桌面應用視覺表現的長期空白。掌握「不透明度、形狀剪裁、逐像素透明」三大核心能力,配合 RepaintManager、離屏緩衝、抗鋸齒繪製,即可構建媲美原生應用的炫酷 UI。現代專案建議直接使用 Java 7+ 標準 API,並做好跨平台 Compositing 環境相容性測試。