透明和不規則 Swing 視窗

來源:互聯網
上載者:User
 透明和不規則 Swing 視窗








 
 

Print-friendly Version



By Kirill Grouchnikov,
4/22/08

 

支援透明和不規則視窗已經成為 AWT 和 Swing
團隊長久以來夢寐以求的功能。儘管本機應用程式在主要作業系統上使用這項功能已經為時已久,但在核心 Java 中還不能使用它。即將發布的
“Consumer JRE”進行中修改,也就是對 Java SE 6 進行重大更新。Java SE 6
將為建立不規則、全透明和每個像素透明的最上層視窗提供 API。

曆史

本機應用程式的開發人員通常在開發 UI
應用程式中享受了更進階的靈活性。但是為此而付出的代價是將應用程式限制在某一特定平台上,在許多情況中,這種靈活性不如獲得更為豐富的 UI
體驗和案頭緊密整合那麼重要。從傳統上講,跨平台 UI 工具箱,例如 Swing、SWT、QT 和 wxWidgets 趨向於被動應付眾所周知的兩難問題。當只有某些目標平台支援所要求的功能時怎麼辦?在這種情況下,類比缺失的功能可能只會讓您南轅北轍。

不規則和透明視窗是跨平台 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 開發的 JNA 庫。該庫在 2007 年問世,Timothy 對於 不規則視窗 和 字母掩碼透明度 已經發表過部落格。

第一種方法的主要問題是要使用 Robot
類。即使您有許可權獲得螢幕,您也必須在顯示視窗之前完成。此外,如何保持案頭背景同步處理?假設在後台現正播放 YouTube
視頻。與視窗產生的事件不同( 調整大小,移動 ),AWT 並不在任何交叉視窗的重畫上提供註冊接聽程式的任何方式。雖然 Chris 和
Joshua 通過在至少每秒內進行快照提供解決方案,這對於覆蓋後台視頻播放還不夠。而且在每次快照前需要對視窗加以隱藏;這可能導致可見的閃爍。

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

Consumer JRE

Java SE 6 Update N, 通常稱作 Consumer JRE, 是 Sun 公司的努力成果,為重新設定 Java 將其作為開發富傳統型應用程式的可行方法。在 Consumer JRE 中的新功能和主要改進列表相當廣泛,並將特別閃耀的寶石隱藏在最新一周構建代碼之一的版本資訊中。Bug 6633275 被簡單地賦予“需要支援不規則/透明視窗”的標題。但是該實現核心 JDK 新功能的可能性所帶給 Swing 開發人員的意義是深遠的。本文的剩餘部分將顯示能夠實現和如何?該功能的幾個樣本。

在進一步研究之前,有一個非常重要的注意事項。由於 Consumer JRE 被官方認為是對穩定 JDK 發行的一個次要更新,因此在“公用”包中不能添加任何新的 API( 類、方法等等 ),例如 java.awtjavax.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,從 Looks 項目安裝提供拖放陰影的自訂邊框:


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());
}

現在我們需要重寫 Popupshow() 方法來標記整個快顯視窗為透明樣式。這要求拖放陰影邊框的每個像素具有透明性。


@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 的幾個構造塊並將它們擴充到“楨外”。它還有一個重畫管理器 ( 要瞭解關於重畫管理器方面的詳細資料,請參見 使用重畫管理器的驗證覆蓋 條目 )以便將主楨內容與反射視窗保持同步。還需要在主楨上註冊組件接聽程式和視窗接聽程式以便確保反射視窗與主視窗的可見度、位置和大小保持同步。除此之外,還要有一個自訂窗格將其內容繪畫到脫屏緩衝區。脫屏緩衝區被用於繪畫主楨和在反射視窗內的反射。

讓我們看一下代碼。主類是擴充 JFrameJReflectionFrame。構造器建立了反射視窗並向其中添加非雙重緩衝和透明的面板。還重寫了面板的 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 的三個樣本。我確信您能想出更多的例子。

參考資料
  • 本文的 範例程式碼。
  • Swing 繪畫流水線的 概述
  • 關於使用 Robot 類來類比透明視窗的相關 章節, 出自 Joshua Marinacci 和 Chris Adamson 編著的Swing Hacks
  • 由 Timothy Wall 編著的 JNA 項目
  • 在 Consumer JRE 中建立透明和不規則視窗的基本 API 介紹
  • 在 Consumer JRE 中建立每像素透明和柔性裁剪的不規則視窗的 介紹
  • 由 Romain Guy 編寫的關於建立透明反射 教程
  • 由 Chris Campbell 編寫的關於柔性裁剪 教程
  • 視頻反射組件的 概述,出自 Chet Haase 和 Romain Guy 編著的《骯髒的富客戶機》

Kirill Grouchnikov 在讀初中時就開始編寫軟體,獲得電腦科學專業的學士學位後,他繼續從事這方面的工作。他感興趣的主要領域包括傳統型應用程式、成像演算法和進階 UI 技術。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.