JDK 6 u10 中 Swing最新特性:增加透明窗體及不規則窗體功能:com.sun.awt.AWTUtilities

支持透明和不規則窗口已經成爲 AWT 和 Swing 團隊長久以來夢寐以求的功能。儘管本機應用程序在主要操作系統上使用這項功能已經爲時已久,但在覈心 Java 中還不能使用它。即將發佈的 “Consumer JRE”正在進行修改,也就是對 Java SE 6 進行重大更新。Java SE 6 將爲創建不規則、全透明和每個像素透明的頂級窗口提供 API。­

歷史本機應用程序的開發人員通常在開發 UI 應用程序中享受了更高級的靈活性。但是爲此而付出的代價是將應用程序限制在某一特定平臺上,在許多情況中,這種靈活性不如獲得更爲豐富的 UI 體驗和桌面緊密集成那麼重要。從傳統上講,跨平臺 UI 工具箱,例如 Swing、SWT、QTwxWidgets 趨向於被動應付衆所周知的兩難問題。當只有某些目標平臺支持所要求的功能時怎麼辦?在這種情況下,模擬缺失的功能可能只會讓您南轅北轍。­

不規則和透明窗口是跨平臺 UI 工具箱侷限性的最好例子。如果在特定目標平臺不支持此項功能,那麼在該平臺上就沒有什麼更多事情要做了,此項功能可能用作強有力的參數向工具箱添加該項功能。但是,Swing 開發人員社區長久以來一直爭論主要目標平臺不久就會提供這些功能。事實上,Windows 自從 Windows 95 (參見 MSDN 上的 SetWindowRgn 文檔 )就已經支持不規則窗口了。在 X11 中匹配功能自從 1989 年 ( 參見 X Nonrectangular Window Shape Extension Library PDF 文檔 )就已經可用了。在 OS X 中您僅能在 JFrame 上設置透明的背景顏色。­

直到現在,對跨平臺透明和不規則窗口有興趣的 Swing 應用程序有三種主要可選方式。­

在顯示目標窗口之前使用 java.awt.Robot 捕獲桌面。這種方法在 Joshua Marinacci 和 Chris Adamson 編寫的 《 Swing Hacks 》 書中的 第 41 章 中已經進行了評述。 使用 JNI 包裝目標平臺的本機 API。 使用由 Timothy Wall 開發的 [url=https://jna.dev.java.net/]JNA 庫[/url]。該庫在 2007 年問世,Timothy 對於 不規則窗口字母掩碼透明度 已經發表過博客。 第一種方法的主要問題是要使用 Robot 類。即使您有權限獲得屏幕截圖,您也必須在顯示窗口之前完成。此外,如何保持桌面後臺同步?假設在後臺正在播放 YouTube 視頻。與窗口生成的事件不同( 調整大小,移動 ),AWT 並不在任何交叉窗口的重畫上提供註冊偵聽器的任何方式。雖然 Chris 和 Joshua 通過在至少每秒內進行快照提供解決方法,這對於覆蓋後臺視頻播放還不夠。而且在每次快照前需要對窗口加以隱藏;這可能導致可見的閃爍。­

使用 JNI 和 JNA 導致顯著的視覺保真性改進。純 JNI 會帶來開銷的急劇下降:您必須將目標平臺的每一個相關的 API 綁定,還要捆綁本機庫。JNA 爲您分擔這項重任; 它捆綁主機庫並提供能在運行時提取並加載它們的類加載器。它支持 Linux、 OS X、 Windows、 Solaris 和 FreeBSD。­

Consumer JRE[url=https://jdk6.dev.java.net/6uNea.html]Java SE 6 Update N[/url], 通常稱作 Consumer JRE, 是 Sun 公司的努力成果,爲重新配置 Java 將其作爲開發富桌面應用程序的可行方法。在 Consumer JRE 中的新功能和主要改進列表相當廣泛,並將特別閃耀的寶石隱藏在最新一週構建代碼之一的發行說明中。Bug 6633275 被簡單地賦予“需要支持不規則/透明窗口”的標題。但是該實現核心 JDK 新功能的可能性所帶給 Swing 開發人員的意義是深遠的。本文的剩餘部分將顯示能夠實現和如何實現該功能的幾個示例。­

在進一步研究之前,有一個非常重要的注意事項。由於 Consumer JRE 被官方認爲是對穩定 JDK 發行的一個次要更新,因此在“公共”包中不能添加任何新的 API( 類、方法等等 ),例如 java.awt 或 javax.swing。在本文中討論的所有 API 在新 com.sun.awt.AWTUtilities 類中出現,該類不是官方支持的部分 API。它在 Java SE 7 中的位置最有可能發生改變,簽名方法可能在現在和最終的 Consumer JRE 發行之間發生輕微變化。所以當這種改變發生時準備更改您自己的代碼。­

AWTUtilities 類我首先討論 com.sun.awt.AWTUtilities 類,請參見 在覈心 Java 中的透明和不規則窗口 博客條目。首先我們從圖 1 中的簡單窗口入手:­

圖片­

­

­

圖 1. 帶有控件的窗口­

要使窗口透明,您可以使用 AWTUtilities.setWindowOpacity(Window, float) 方法,如圖 2 所示:­

圖片­

­

­

­

­

圖 2. 相同的窗口,但是有 50% 的不透明度­

要使窗口不規則,您可以使用 AWTUtilities.setWindowShape(Window, Shape) 方法,如圖 3 所示:­

圖片­

­

­

圖 3. 相同的窗口,但是被一個橢圓剪裁 ­

正如您從圖 3 中能看到的,不規則的窗口看起來不是很好。窗口的邊緣呈鋸齒狀並且整體印象也不是很乾淨。要獲得不規則窗口的更佳視覺效果,您必須使用 AWTUtilities.setWindowOpaque(Window, boolean) API,並使用柔性裁剪繪畫窗口背景。這在後續的 Swing 窗口的柔性裁剪和每像素透明度 博客條目中進行了闡明。對於窗口的左上角和右上角,該條目採用 Chris Campbell 的 柔性裁剪教程 以及 Romain Guy 的 反射教程, 其中包括 Sebastien Petrucci 的改進。圖 4 顯示了每個像素透明的柔性裁剪窗口:­

圖片­

­

­

圖 4. 柔性裁剪和每個像素透明的窗口­

現在我們手頭上已經有了這些 API,我們打算做些什麼呢?對它們進行探索這種可能性當然是另人好奇的,我們正打算看看幾個多樣混合的示例。­

工具提示讓我們使應用工具提示變得透明怎麼樣?對於輕量級工具提示,實現這一目標是相當容易的,因爲它們被作爲 Swing 頂級窗口的一部分加以繪畫。( 要獲得關於輕量級彈出菜單的詳細信息,請參見 玻璃窗格和輕量級彈出菜單 條目。)但是,一旦工具提示成爲重量級並“打破”窗口綁定,您必須繼續採用 Robot 或 JNI/JNA。現在讓我們看一看使用 AWTUtilities API 如何完成這項任務。­

javax.swing.PopupFactory 是創建彈出菜單的廠。工具提示只是彈出功能的一個例子;其他例子包括組合框下拉列表和菜單。PopupFactory.setSharedInstance 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,將工具提示的不透明度設置爲 0.8,從 [url=https://looks.dev.java.net/]Looks[/url] 項目安裝提供拖放陰影的自定義邊框:­

­

   TranslucentPopup(Component owner, Component contents, int ownerX, int ownerY) {­

      // create a new heavyweight window­

      this.popupWindow = new JWindow();­

      // mark the popup with partial opacity­

      com.sun.awt.AWTUtilities.setWindowOpacity(popupWindow,­

            (contents instanceof JToolTip) ? 0.8f : 0.95f);­

      // determine the popup location­

      popupWindow.setLocation(ownerX, ownerY);­

      // add the contents to the popup­

      popupWindow.getContentPane().add(contents, BorderLayout.CENTER);­

      contents.invalidate();­

      JComponent parent = (JComponent) contents.getParent();­

      // set the shadow border­

      parent.setBorder(new ShadowPopupBorder());­

   }­

­

現在我們需要重寫 Popup 的 show() 方法來標記整個彈出窗口爲透明樣式。這要求拖放陰影邊框的每個像素具有透明性。­

­

   @Override­

   public void show() {­

      this.popupWindow.setVisible(true);­

      this.popupWindow.pack();­

      // mark the window as non-opaque, so that the­

      // shadow border pixels take on the per-pixel­

      // translucency­

      com.sun.awt.AWTUtilities.setWindowOpaque(this.popupWindow, false);­

   }­

­

hide() 方法只是隱藏並處置彈出窗口:­

­

   @Override­

   public void hide() {­

      this.popupWindow.setVisible(false);­

      this.popupWindow.removeAll();­

      this.popupWindow.dispose();­

   }­

­

要安裝該彈出窗口,僅簡單調用­

­

      PopupFactory.setSharedInstance(new TranslucentPopupFactory());­

­

圖 5 顯示了一個具有透明工具提示的示例幀。注意,與工具提示保持視覺(透明性和拖放陰影邊框)上的一致性跨越 Swing 幀綁定並擴展到後臺 Eclipse 窗口。­

圖片­

­

­

圖 5. 工具提示­

現在我們做相同的動畫。當工具提示顯示時將顏色調淡些,當它被隱藏起來時把它的顏色漸隱如何?一旦您熟悉了 AWTUtilities API,上述操作不難實現。下面給出 show() 方法的代碼:­

­

   @Override­

   public void show() {­

      if (this.toFade) {­

         // mark the popup with 0% opacity­

         this.currOpacity = 0;­

         com.sun.awt.AWTUtilities.setWindowOpacity(popupWindow, 0.0f);­

      }­

­

      this.popupWindow.setVisible(true);­

      this.popupWindow.pack();­

­

      // mark the window as non-opaque, so that the­

      // shadow border pixels take on the per-pixel­

      // translucency­

      com.sun.awt.AWTUtilities.setWindowOpaque(this.popupWindow, false);­

­

      if (this.toFade) {­

         // start fading in­

         this.fadeInTimer = new Timer(50, new ActionListener() {­

            public void actionPerformed(ActionEvent e) {­

               currOpacity += 20;­

               if (currOpacity <= 100) {­

                  com.sun.awt.AWTUtilities.setWindowOpacity(popupWindow,­

                        currOpacity / 100.0f);­

                  // workaround bug 6670649 - should call­

                  // popupWindow.repaint() but that will not repaint the­

                  // panel­

                  popupWindow.getContentPane().repaint();­

               } else {­

                  currOpacity = 100;­

                  fadeInTimer.stop();­

               }­

            }­

         });­

         this.fadeInTimer.setRepeats(true);­

         this.fadeInTimer.start();­

      }­

   }­

­

這時我們用 0% 的不透明度標記彈出窗口。然後我們啓動重複計時器進行五次迭代。每一次跌代我們增加窗口不透明度 20% 並重新繪畫。最後我們停止計時器。最終的視覺結果是工具提示外觀的平滑退色序列,這一序列持續大約 250 毫秒。­

hide() 方法非常類似:­

­

   @Override­

   public void hide() {­

      if (this.toFade) {­

         // cancel fade-in if it's running.­

         if (this.fadeInTimer.isRunning())­

            this.fadeInTimer.stop();­

­

         // start fading out­

         this.fadeOutTimer = new Timer(50, new ActionListener() {­

            public void actionPerformed(ActionEvent e) {­

               currOpacity -= 10;­

               if (currOpacity >= 0) {­

                  com.sun.awt.AWTUtilities.setWindowOpacity(popupWindow,­

                        currOpacity / 100.0f);­

                  // workaround bug 6670649 - should call­

                  // popupWindow.repaint() but that will not repaint the­

                  // panel­

                  popupWindow.getContentPane().repaint();­

               } else {­

                  fadeOutTimer.stop();­

                  popupWindow.setVisible(false);­

                  popupWindow.removeAll();­

                  popupWindow.dispose();­

                  currOpacity = 0;­

               }­

            }­

         });­

         this.fadeOutTimer.setRepeats(true);­

         this.fadeOutTimer.start();­

      } else {­

         popupWindow.setVisible(false);­

         popupWindow.removeAll();­

         popupWindow.dispose();­

      }­

   }­

­

首先檢查退色序列是否仍在運行,根據需要將它刪除。然後,不立即隱藏窗口,而是將不透明度以 10% 的增量從 100% 改爲 0(因此漸隱序列是退色序列的兩倍)然後隱藏並處置彈出窗口。注意兩種方法參閱了 Boolean toFade 變量 —— 它在工具提示上被設置爲 true。彈出窗口的其他類型(菜單、組合框下拉列表)沒有退色動畫。­

視頻反射現在讓我們做些更爲激動人心的事情。在 Romain Guy 的博客條目 重畫管理器演示(第 11 章) 中,它顯示了提供反射功能的 Swing 組件。從他與 Chet Haase 合著的 《 骯髒的富客戶機 》 書中抽取一段測試應用程序,其中顯示該組件提供了 QuickTime 電影的實時反射。在窗口綁定 之外 進行反射如何?­

首先要有實際應用中的反射幀的屏幕截圖。圖 6 顯示了正在播放 “Get a Mac” 廣告的形狀規則的 Swing 幀( 使用嵌入式 QuickTime 播放器 ), 伴隨着覆蓋桌面的透明的實時反射:­

圖片­

­

­

圖 6. 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 non-double buffered and non-opaque­

      // to make it translucent.­

      reflectionPanel.setDoubleBuffered(false);­

      reflectionPanel.setOpaque(false);­

­

      reflection.setLayout(new BorderLayout());­

      reflection.add(reflectionPanel, BorderLayout.CENTER);­

­

      // register listeners - see below­

      ...­

­

      // initialize the reflection size and location­

      reflection.setSize(getSize());­

      reflection.setLocation(getX(), getY() + getHeight());­

      reflection.setVisible(true);­

­

      // install custom repaint manager to force re-painting­

      // the reflection when something in the main window is­

      // repainted­

      RepaintManager.setCurrentManager(new ReflectionRepaintManager());­

   }­

­

下面是保持反射窗口與主楨同步的偵聽器:­

­

      this.addComponentListener(new ComponentAdapter() {­

         @Override­

         public void componentHidden(ComponentEvent e) {­

            reflection.setVisible(false);­

         }­

­

         @Override­

         public void componentMoved(ComponentEvent e) {­

            // update the reflection location­

            reflection.setLocation(getX(), getY() + getHeight());­

         }­

­

         @Override­

         public void componentResized(ComponentEvent e) {­

            // update the reflection size and location­

            reflection.setSize(getWidth(), getHeight());­

            reflection.setLocation(getX(), getY() + getHeight());­

         }­

­

         @Override­

         public void componentShown(ComponentEvent e) {­

            reflection.setVisible(true);­

­

            // if the reflection window is opaque, mark­

            // it as per-pixel translucent­

            if (com.sun.awt.AWTUtilities.isWindowOpaque(reflection)) {­

               com.sun.awt.AWTUtilities.setWindowOpaque(reflection, false);­

            }­

         }­

      });­

­

      this.addWindowListener(new WindowAdapter() {­

         @Override­

         public void windowActivated(WindowEvent e) {­

            // force showing the reflection window­

            reflection.setAlwaysOnTop(true);­

            reflection.setAlwaysOnTop(false);­

         }­

      });­

­

重畫管理器相當簡單:它強制主楨的整個根窗格重畫,然後更新反射窗口。這樣可以最優化更新區域反射的同步,對於示例應用程序要達到的目的,這點就足夠了。­

­

   private class ReflectionRepaintManager extends RepaintManager {­

      @Override­

      public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {­

         Window win = SwingUtilities.getWindowAncestor(c);­

         if (win instanceof JReflectionFrame) {­

            // mark the entire root pane to be repainted­

            JRootPane rp = ((JReflectionFrame) win).getRootPane();­

            super.addDirtyRegion(rp, 0, 0, rp.getWidth(), rp.getHeight());­

­

            // workaround bug 6670649 - should call reflection.repaint()­

            // but that will not repaint the panel­

            reflectionPanel.repaint();­

         } else {­

            super.addDirtyRegion(c, x, y, w, h);­

         }­

      }­

   }­

­

主楨 (脫屏緩衝區) 和反射窗口的繪圖代碼在 Romain 的 反射教程 中進行了詳細描述。­

結束語對這一結果我們期待已久,現在終於如願以償。儘管創建透明和不規則窗口的 API 還沒有官方支持的包,但是它們仍可用於創建可視的富跨平臺 UI。從 Romain 的博客 透明和不規則窗口( Extreme GUI Makeover ) 條目展示 JNA 項目,用於創建動畫的透明不規則窗口的可視化競爭應用。現在您可以使用核心 JDK 做同樣的處理。本文全面介紹了顯示實際應用中的核心 JDK API 的三個示例。我確信您能想出更多的例子。­

­

原文地址:­

http://developers.sun.com.cn/Java/translucent-and-shaped-swing-windows.html­

­

­

­

透明和不規則 Swing 窗口­

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章