支持透明和不規則窗口已經成為 AWT 和 Swing 團隊長久以來夢寐以求的功能盡管本機應用程序在主要操作系統上使用這項功能已經為時已久但在核心 Java 中還不能使用它即將發布的 Consumer JRE正在進行修改也就是對 Java SE 進行重大更新Java SE 將為創建不規則全透明和每個像素透明的頂級窗口提供 API
歷史本機應用程序的開發人員通常在開發 UI 應用程序中享受了更高級的靈活性但是為此而付出的代價是將應用程序限制在某一特定平台上在許多情況中這種靈活性不如獲得更為豐富的 UI 體驗和桌面緊密集成那麼重要從傳統上講跨平台 UI 工具箱例如 SwingSWTQT 和 wxWidgets 趨向於被動應付眾所周知的兩難問題當只有某些目標平台支持所要求的功能時怎麼辦?在這種情況下模擬缺失的功能可能只會讓您南轅北轍
不規則和透明窗口是跨平台 UI 工具箱局限性的最好例子如果在特定目標平台不支持此項功能那麼在該平台上就沒有什麼更多事情要做了此項功能可能用作強有力的參數向工具箱添加該項功能但是Swing 開發人員社區長久以來一直爭論主要目標平台不久就會提供這些功能事實上Windows 自從 Windows (參見 MSDN 上的 SetWindowRgn 文檔 )就已經支持不規則窗口了在 X 中匹配功能自從 年 ( 參見 X Nonrectangular Window Shape Extension Library PDF 文檔 )就已經可用了在 OS X 中您僅能在 JFrame 上設置透明的背景顏色
直到現在對跨平台透明和不規則窗口有興趣的 Swing 應用程序有三種主要可選方式
在顯示目標窗口之前使用 javaawtRobot 捕獲桌面這種方法在 Joshua Marinacci 和 Chris Adamson 編寫的 《 Swing Hacks 》 書中的 第 章 中已經進行了評述
使用 JNI 包裝目標平台的本機 API使用由 Timothy Wall 開發的 JNA 庫該庫在 年問世Timothy 對於 不規則窗口 和 字母掩碼透明度 已經發表過博客
第一種方法的主要問題是要使用 Robot 類即使您有權限獲得屏幕截圖您也必須在顯示窗口之前完成此外如何保持桌面後台同步?假設在後台正在播放 YouTube 視頻與窗口生成的事件不同( 調整大小移動 )AWT 並不在任何交叉窗口的重畫上提供注冊偵聽器的任何方式雖然 Chris 和 Joshua 通過在至少每秒內進行快照提供解決方法這對於覆蓋後台視頻播放還不夠而且在每次快照前需要對窗口加以隱藏這可能導致可見的閃爍
使用 JNI 和 JNA 導致顯著的視覺保真性改進純 JNI 會帶來開銷的急劇下降您必須將目標平台的每一個相關的 API 綁定還要捆綁本機庫JNA 為您分擔這項重任 它捆綁主機庫並提供能在運行時提取並加載它們的類加載器它支持 Linux OS X Windows Solaris 和 FreeBSD
Consumer JRE Java SE Update N 通常稱作 Consumer JRE 是 Sun 公司的努力成果為重新配置 Java 將其作為開發富桌面應用程序的可行方法在 Consumer JRE 中的新功能和主要改進列表相當廣泛並將特別閃耀的寶石隱藏在最新一周構建代碼之一的發行說明中Bug 被簡單地賦予需要支持不規則/透明窗口的標題但是該實現核心 JDK 新功能的可能性所帶給 Swing 開發人員的意義是深遠的本文的剩余部分將顯示能夠實現和如何實現該功能的幾個示例
在進一步研究之前有一個非常重要的注意事項由於 Consumer JRE 被官方認為是對穩定 JDK 發行的一個次要更新因此在公共包中不能添加任何新的 API( 類方法等等 )例如 javaawt 或 javaxswing在本文中討論的所有 API 在新 comsunawtAWTUtilities 類中出現該類不是官方支持的部分 API它在 Java SE 中的位置最有可能發生改變簽名方法可能在現在和最終的 Consumer JRE 發行之間發生輕微變化所以當這種改變發生時准備更改您自己的代碼
AWTUtilities 類我首先討論 comsunawtAWTUtilities 類請參見 在核心 Java 中的透明和不規則窗口 博客條目首先我們從圖 中的簡單窗口入手
圖 帶有控件的窗口
要使窗口透明您可以使用 AWTUtilitiessetWindowOpacity(Window float) 方法如圖 所示
圖 相同的窗口但是有 % 的不透明度
要使窗口不規則您可以使用 AWTUtilitiessetWindowShape(Window Shape) 方法如圖 所示
圖 相同的窗口但是被一個橢圓剪裁
正如您從圖 中能看到的不規則的窗口看起來不是很好窗口的邊緣呈鋸齒狀並且整體印象也不是很干淨要獲得不規則窗口的更佳視覺效果您必須使用 AWTUtilitiessetWindowOpaque(Window boolean) API並使用柔性裁剪繪畫窗口背景這在後續的 Swing 窗口的柔性裁剪和每像素透明度 博客條目中進行了闡明對於窗口的左上角和右上角該條目采用 Chris Campbell 的 柔性裁剪教程 以及 Romain Guy 的 反射教程 其中包括 Sebastien Petrucci 的改進圖 顯示了每個像素透明的柔性裁剪窗口
圖 柔性裁剪和每個像素透明的窗口
現在我們手頭上已經有了這些 API我們打算做些什麼呢?對它們進行探索這種可能性當然是另人好奇的我們正打算看看幾個多樣混合的示例
工具提示讓我們使應用工具提示變得透明怎麼樣?對於輕量級工具提示實現這一目標是相當容易的因為它們被作為 Swing 頂級窗口的一部分加以繪畫( 要獲得關於輕量級彈出菜單的詳細信息請參見 玻璃窗格和輕量級彈出菜單 條目)但是一旦工具提示成為重量級並打破窗口綁定您必須繼續采用 Robot 或 JNI/JNA現在讓我們看一看使用 AWTUtilities API 如何完成這項任務
javaxswingPopupFactory 是創建彈出菜單的廠工具提示只是彈出功能的一個例子其他例子包括組合框下拉列表和菜單PopupFactorysetSharedInstance API 可以被用於設置自定義彈出廠這就是我們想要做的當前的彈出廠被用於創建所有應用彈出窗口我們將在所有的工具提示上安裝自定義不透明廠
核心彈出廠的實現是相當復雜的首先嘗試創建輕量級彈出窗口當要求創建重量級窗口時系統要管理高速緩存以便重用先前創建的彈出窗口實現過程將創建一個新的重量級彈出窗口在相對較新的膝上型電腦上運行不同的方案還未顯示任何突出的性能突破讓我們從自定義彈出廠著手研究
public class TranslucentPopupFactory extends PopupFactory { @Override public Popup getPopup(Component owner Component contents int x int y) throws IllegalArgumentException { // A more complete implementation would cache and reuse // popups return new TranslucentPopup(owner contents x y) }}TranslucentPopup 的實現相當簡單構造器創建新的 JWindow將工具提示的不透明度設置為 從 Looks 項目安裝提供拖放陰影的自定義邊框
TranslucentPopup(Component owner Component contents int ownerX int ownerY) { // create a new heavyweight window thispopupWindow = new JWindow() // mark the popup with partial opacity comsunawtAWTUtilitiessetWindowOpacity(popupWindow (contents instanceof JToolTip) ? f f) // determine the popup location popupWindowsetLocation(ownerX ownerY) // add the contents to the popup popupWindowgetContentPane()add(contents BorderLayoutCENTER) contentsinvalidate() JComponent parent = (JComponent) contentsgetParent() // set the shadow border parentsetBorder(new ShadowPopupBorder()) }現在我們需要重寫 Popup 的 show() 方法來標記整個彈出窗口為透明樣式這要求拖放陰影邊框的每個像素具有透明性
@Override public void show() { thispopupWindowsetVisible(true) thispopupWindowpack() // mark the window as nonopaque so that the // shadow border pixels take on the perpixel // translucency comsunawtAWTUtilitiessetWindowOpaque(thispopupWindow false) }hide() 方法只是隱藏並處置彈出窗口
@Override public void hide() { thispopupWindowsetVisible(false) thispopupWindowremoveAll() thispopupWindowdispose() }要安裝該彈出窗口僅簡單調用
PopupFactorysetSharedInstance(new TranslucentPopupFactory()) 圖 顯示了一個具有透明工具提示的示例幀注意與工具提示保持視覺(透明性和拖放陰影邊框)上的一致性跨越 Swing 幀綁定並擴展到後台 Eclipse 窗口
圖
工具提示
現在我們做相同的動畫當工具提示顯示時將顏色調淡些當它被隱藏起來時把它的顏色漸隱如何?一旦您熟悉了 AWTUtilities API上述操作不難實現下面給出 show() 方法的代碼
@Override public void show() { if (thistoFade) { // mark the popup with % opacity thiscurrOpacity = comsunawtAWTUtilitiessetWindowOpacity(popupWindow f) } thispopupWindowsetVisible(true) thispopupWindowpack() // mark the window as nonopaque so that the // shadow border pixels take on the perpixel // translucency comsunawtAWTUtilitiessetWindowOpaque(thispopupWindow false) if (thistoFade) { // start fading in thisfadeInTimer = new Timer( new ActionListener() { public void actionPerformed(ActionEvent e) { currOpacity += if (currOpacity <= ) { comsunawtAWTUtilitiessetWindowOpacity(popupWindow currOpacity / f) // workaround bug should call // popupWindowrepaint() but that will not repaint the // panel popupWindowgetContentPane()repaint() } else { currOpacity = fadeInTimerstop() } } }) thisfadeInTimersetRepeats(true) thisfadeInTimerstart() } }這時我們用 0% 的不透明度標記彈出窗口然後我們啟動重復計時器進行五次迭代每一次跌代我們增加窗口不透明度 % 並重新繪畫最後我們停止計時器最終的視覺結果是工具提示外觀的平滑退色序列這一序列持續大約 毫秒
hide() 方法非常類似
@Override public void hide() { if (thistoFade) { // cancel fadein if its running if (thisfadeInTimerisRunning()) thisfadeInTimerstop() // start fading out thisfadeOutTimer = new Timer( new ActionListener() { public void actionPerformed(ActionEvent e) { currOpacity = if (currOpacity >= ) { comsunawtAWTUtilitiessetWindowOpacity(popupWindow currOpacity / f) // workaround bug should call // popupWindowrepaint() but that will not repaint the // panel popupWindowgetContentPane()repaint() } else { fadeOutTimerstop() popupWindowsetVisible(false) popupWindowremoveAll() popupWindowdispose() currOpacity = } } }) thisfadeOutTimersetRepeats(true) thisfadeOutTimerstart() } else { popupWindowsetVisible(false) popupWindowremoveAll() popupWindowdispose() } }首先檢查退色序列是否仍在運行根據需要將它刪除然後不立即隱藏窗口而是將不透明度以 % 的增量從 % 改為 (因此漸隱序列是退色序列的兩倍)然後隱藏並處置彈出窗口注意兩種方法參閱了 Boolean toFade 變量 —— 它在工具提示上被設置為 true彈出窗口的其他類型(菜單組合框下拉列表)沒有退色動畫
視頻反射現在讓我們做些更為激動人心的事情在 Romain Guy 的博客條目 重畫管理器演示(第 章) 中它顯示了提供反射功能的 Swing 組件從他與 Chet Haase 合著的 《 骯髒的富客戶機 》 書中抽取一段測試應用程序其中顯示該組件提供了 QuickTime 電影的實時反射在窗口綁定 之外 進行反射如何?
首先要有實際應用中的反射幀的屏幕截圖圖 顯示了正在播放 Get a Mac 廣告的形狀規則的 Swing 幀( 使用嵌入式 QuickTime 播放器 ) 伴隨著覆蓋桌面的透明的實時反射
圖 QuickTime 電影的反射
該實現重用了來自 Romain 的幾個構造塊並將它們擴展到桢外它還有一個重畫管理器 ( 要了解關於重畫管理器方面的詳細信息請參見 使用重畫管理器的驗證覆蓋 條目 )以便將主桢內容與反射窗口保持同步還需要在主桢上注冊組件偵聽器和窗口偵聽器以便確保反射窗口與主窗口的可見性位置和大小保持同步除此之外還要有一個自定義窗格將其內容繪畫到脫屏緩沖區脫屏緩沖區被用於繪畫主桢和在反射窗口內的反射
讓我們看一下代碼主類是擴展 JFrame 的 JReflectionFrame構造器創建了反射窗口並向其中添加非雙重緩沖和透明的面板還重寫了面板的 paintComponent() 以便繪畫主桢內容的反射在初始化反射桢的位置和大小後我們安裝了一個自定義重畫管理器
public JReflectionFrame(String title) { super(title) reflection = new JWindow() reflectionPanel = new JPanel() { @Override protected void paintComponent(Graphics g) { // paint the reflection of the main window paintReflection(g) } } // mark the panel as nondouble buffered and nonopaque // to make it translucent reflectionPanelsetDoubleBuffered(false) reflectionPanelsetOpaque(false) reflectionsetLayout(new BorderLayout()) reflectionadd(reflectionPanel BorderLayoutCENTER) // register listeners see below …… // initialize the reflection size and location reflectionsetSize(getSize()) reflectionsetLocation(getX() getY() + getHeight()) reflectionsetVisible(true) // install custom repaint manager to force repainting // the reflection when something in the main window is // repainted RepaintManagersetCurrentManager(new ReflectionRepaintManager()) }下面是保持反射窗口與主桢同步的偵聽器
thisaddComponentListener(new ComponentAdapter() { @Override public void componentHidden(ComponentEvent e) { reflectionsetVisible(false) } @Override public void componentMoved(ComponentEvent e) { // update the reflection location reflectionsetLocation(getX() getY() + getHeight()) } @Override public void componentResized(ComponentEvent e) { // update the reflection size and location reflectionsetSize(getWidth() getHeight()) reflectionsetLocation(getX() getY() + getHeight()) } @Override public void componentShown(ComponentEvent e) { reflectionsetVisible(true) // if the reflection window is opaque mark // it as perpixel translucent if (comsunawtAWTUtilitiesisWindowOpaque(reflection)) { comsunawtAWTUtilitiessetWindowOpaque(reflection false) } } }) thisaddWindowListener(new WindowAdapter() { @Override public void windowActivated(WindowEvent e) { // force showing the reflection window reflectionsetAlwaysOnTop(true) reflectionsetAlwaysOnTop(false) } }) 重畫管理器相當簡單它強制主桢的整個根窗格重畫然後更新反射窗口這樣可以最優化更新區域反射的同步對於示例應用程序要達到的目的這點就足夠了
private class ReflectionRepaintManager extends RepaintManager { @Override public void addDirtyRegion(JComponent c int x int y int w int h) { Window win = SwingUtilitiesgetWindowAncestor(c) if (win instanceof JReflectionFrame) { // mark the entire root pane to be repainted JRootPane rp = ((JReflectionFrame) win)getRootPane() superaddDirtyRegion(rp rpgetWidth() rpgetHeight()) // workaround bug should call reflectionrepaint() // but that will not repaint the panel reflectionPanelrepaint() } else { superaddDirtyRegion(c x y w h) } } }主桢 (脫屏緩沖區) 和反射窗口的繪圖代碼在 Romain 的 反射教程 中進行了詳細描述
結束語對這一結果我們期待已久現在終於如願以償盡管創建透明和不規則窗口的 API 還沒有官方支持的包但是它們仍可用於創建可視的富跨平台 UI從 Romain 的博客 透明和不規則窗口( Extreme GUI Makeover ) 條目展示 JNA 項目用於創建動畫的透明不規則窗口的可視化競爭應用現在您可以使用核心 JDK 做同樣的處理本文全面介紹了顯示實際應用中的核心 JDK API 的三個示例我確信您能想出更多的例子
From:http://tw.wingwit.com/Article/program/Java/hx/201311/25650.html