探秘JDK 7之三:JLayer裝飾Swing組件
51CTO在給各位介紹過“探秘JDK 7”和“再探JDK 7”之后,今天我們?cè)俅巫呱咸矫豃DK 7之旅。本文將為大家介紹JDK 7引入了一個(gè)新的Swing組件來裝飾其它Swing組件,這個(gè)新的組件是通過javax.swing.JLayer類,基于Swing實(shí)驗(yàn)室項(xiàng)目Swing Helper的JXLayer實(shí)現(xiàn)的。JLayer結(jié)合javax.swing.plaf.LayerUI使用可以實(shí)現(xiàn)高級(jí)的繪制效果,并可以在它的邊界范圍內(nèi)接收由java.awt.AWTEvents產(chǎn)生的所有通知。
JLayer和LayerUI概述
根據(jù)JDK的文檔描述,JLayer委托處理LayerUI對(duì)象的繪制和輸入事件,LayerUI負(fù)責(zé)裝飾,你可以使用這些類修改現(xiàn)有組件的外觀和行為使它們的裝飾效果更好。
實(shí)際上,你可以自己動(dòng)手?jǐn)U展LayerUI,廢除它們自帶的方法,自己定制繪制和事件處理方法,然后將這個(gè)類的實(shí)例和裝飾后的組件一道,傳遞給下面的JLayer構(gòu)造器:
public JLayer(V view,LayerUI<V> ui)
第一個(gè)參數(shù)可以是任何類的延伸java.awt.Component,表示你要裝飾的Swing組件,這個(gè)組件可以是一個(gè)JPanel或其它容器,這個(gè)容器和它里面所有的組件都將被裝飾,第二個(gè)參數(shù)代表裝飾器。使用這些構(gòu)造器創(chuàng)建JLayer時(shí),可以延遲指定LayerUI實(shí)例和/或視圖。
如果初始化時(shí)不指定視圖,之后你可以調(diào)用JLayer's public void setView(V view)方法來提供一個(gè)視圖,這個(gè)類也提供了一個(gè)public V getView()方法返回組件是否被裝飾,沒有裝飾就返回null。
如果初始化時(shí)不指定LayerUI實(shí)例,之后你可以調(diào)用JLayer's public void setUI(LayerUI ui方法提供一個(gè)實(shí)例,這個(gè)類也提供了一個(gè)public LayerUI getUI()方法返回當(dāng)前的裝飾器,沒有裝飾器就返回null。
JLayer自定義繪制
為了演示JLayer的自定義繪制特性,我創(chuàng)建了一個(gè)ReverseText程序,其代碼顯示在清單1中,當(dāng)按下按鈕時(shí),輸入到textfield中的文本將全部顛倒,這個(gè)程序使用JLayer在用戶界面后繪制了一個(gè)墻紙圖案。
清單1. ReverseText.java
- // ReverseText.java
- import java.awt.Color;
- import java.awt.EventQueue;
- import java.awt.GradientPaint;
- import java.awt.Graphics;
- import java.awt.Graphics2D;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import javax.swing.JButton;
- import javax.swing.JComponent;
- import javax.swing.JFrame;
- import javax.swing.JLabel;
- import javax.swing.JLayer;
- import javax.swing.JPanel;
- import javax.swing.JTextField;
- import javax.swing.plaf.LayerUI;
- public class ReverseText
- {
- private static Color PALE_YELLOW = new Color (1.0f,1.0f,0.0f,0.2f);
- private static Color PALE_GREEN = new Color (0.0f,1.0f,0.0f,0.2f);
- private static JLayer<JPanel> createLayer ()
- {
- LayerUI<JPanel> layerUI;
- layerUI = new LayerUI<JPanel> ()
- {
- public void paint (Graphics g,JComponent c)
- {
- // Paint the wallpaper.
- Graphics2D g2 = (Graphics2D) g;
- g2.setPaint (new GradientPaint (0,0,PALE_YELLOW,
- 5,0,PALE_GREEN,true));
- g2.fillRect (0,0,c.getWidth (),c.getHeight ());
- // Make sure that layer's panel view is not opaque.
- JLayer l = (JLayer) c;
- if (l.getView ().isOpaque ())
- ((JPanel) l.getView ()).setOpaque (false);
- // Paint the view minus its background.
- super.paint (g,c);
- }
- };
- // Create a user interface to be decorated.
- JPanel pnl = new JPanel ();
- JLabel lblName = new JLabel ("Name:");
- pnl.add (lblName);
- final JTextField txtName = new JTextField (20);
- pnl.add (txtName);
- JButton btnReverse = new JButton ("Reverse");
- pnl.add (btnReverse);
- ActionListener al;
- al = new ActionListener ()
- {
- public void actionPerformed (ActionEvent ae)
- {
- String txt = txtName.getText ();
- txt = new StringBuffer (txt).reverse ().toString ();
- txtName.setText (txt);
- }
- };
- btnReverse.addActionListener (al);
- // Create the layer for the panel using our custom layerUI.
- return new JLayer<JPanel> (pnl,layerUI);
- }
- private static void createAndShowUI ()
- {
- JFrame frame = new JFrame ("Reverse Text");
- frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
- frame.add (createLayer ());
- frame.pack ();
- frame.setLocationRelativeTo (null);
- frame.setVisible (true);
- }
- public static void main (String [] args)
- {
- Runnable r = new Runnable ()
- {
- public void run ()
- {
- createAndShowUI ();
- }
- };
- EventQueue.invokeLater (r);
- }
- }
#p#
其中createLayer()方法是最重要的代碼,它創(chuàng)建了一個(gè)匿名LayerUI子類的實(shí)例,繪制了墻紙和JPanel視圖,創(chuàng)建了UI,在實(shí)例中包含了UI的面板容器。
繪制操作是由LayerUI's public void paint(Graphics g,JComponent c)方法實(shí)現(xiàn)的,第二個(gè)參數(shù)引用了視圖(被裝飾的組件)中的JLayer實(shí)例,不是引用的視圖。
在視圖后創(chuàng)建了漸變渲染墻紙后,調(diào)用paint()方法確保視圖(沒有嵌套面板的單一面板)是透明的,它將會(huì)隱藏墻紙,然后繪制視圖。
paint()對(duì)比paintLayer()
JDK文檔中除了提到paint()方法外,還提到了paintLayer()方法,我這里之所以選擇paint()方法,是因?yàn)長(zhǎng)ayerUI中不存在paintLayer(),此外,文檔還錯(cuò)誤地引用paintLayer() doesn't exist in LayerUI. Furthermore,the documentation incorrectly refers to addPropertyChange(),configureGraphics(),processComponentEvent(),processFocusEvent(),processHierarchyBoundsEvent(),processHierarchyEvent(),processKeyEvent(),processMouseEvent(),processMouseMotionEvent(),processMouseWheelEvent(),and repaintLayer()這些在LayerUI中根本不存在的方法,當(dāng)然這些方法也可能在JDK 7最終發(fā)布時(shí)會(huì)包含進(jìn)來。
圖1顯示了有墻紙背景的UI。
ReverseText程序演示了自定義繪制,避開了事件觸發(fā),不需要檢測(cè)事件,因?yàn)槌绦蛑魂P(guān)心墻紙的繪制效果。相反,清單2顯示了一個(gè)需要響應(yīng)鼠標(biāo)移動(dòng)事件的程序代碼。
清單2. BrandedUI.java
- // BrandedUI.java
- import java.awt.AWTEvent;
- import java.awt.Color;
- import java.awt.Component;
- import java.awt.EventQueue;
- import java.awt.Font;
- import java.awt.GradientPaint;
- import java.awt.Graphics;
- import java.awt.Graphics2D;
- import java.awt.GridLayout;
- import java.awt.Point;
- import java.awt.event.MouseEvent;
- import javax.swing.JComponent;
- import javax.swing.JFrame;
- import javax.swing.JLabel;
- import javax.swing.JLayer;
- import javax.swing.JPanel;
- import javax.swing.JTextField;
- import javax.swing.SwingUtilities;
- import javax.swing.plaf.LayerUI;
- public class BrandedUI
- {
- private static Color PALE_BLUE = new Color (0.0f, 0.0f, 1.0f, 0.3f);
- private static Color PALE_RED = new Color (1.0f, 0.0f, 0.0f, 0.3f);
- private static Font BRAND_FONT = new Font ("Arial", Font.BOLD, 18);
- private static String MSG = "My brand";
- private static JLayer<JPanel> createLayer ()
- {
- LayerUI<JPanel> layerUI;
- layerUI = new LayerUI<JPanel> ()
- {
- private Color color = PALE_BLUE;
- public void installUI (JComponent c)
- {
- super.installUI (c);
- ((JLayer) c).setLayerEventMask (AWTEvent.MOUSE_MOTION_EVENT_MASK);
- }
- public void eventDispatched (AWTEvent e,
- JLayer <? extends JPanel> l)
- {
- MouseEvent me = (MouseEvent) e;
- Point pt = SwingUtilities.convertPoint ((Component) me.getSource (),
- me.getX (), me.getY (), l);
- int cx = l.getWidth ()/2;
- int cy = l.getHeight ()/2;
- if (pt.x > cx-45 && pt.x < cx+45 && pt.y > cy-10 && pt.y < cy+10)
- color = PALE_RED;
- else
- color = PALE_BLUE;
- l.repaint ();
- }
- public void paint (Graphics g, JComponent c)
- {
- // Paint the view.
- super.paint (g, c);
- // Paint the brand.
- g.setColor (color);
- g.setFont (BRAND_FONT);
- int width = g.getFontMetrics ().stringWidth (MSG);
- int height = g.getFontMetrics ().getHeight ();
- g.drawString (MSG, (c.getWidth ()-width)/2,
- c.getHeight ()/2+height/4);
- }
- public void uninstallUI (JComponent c)
- {
- super.uninstallUI (c);
- ((JLayer) c).setLayerEventMask (0);
- }
- };
- // Create a user interface to be decorated.
- JPanel pnlMain = new JPanel ();
- pnlMain.setLayout (new GridLayout (2, 1));
- JPanel pnlTemp = new JPanel ();
- JLabel lblName = new JLabel ("Name:");
- pnlTemp.add (lblName);
- JTextField txtName = new JTextField (20);
- pnlTemp.add (txtName);
- pnlMain.add (pnlTemp);
- pnlTemp = new JPanel ();
- JLabel lblAddr = new JLabel ("Address:");
- pnlTemp.add (lblAddr);
- JTextField txtAddr = new JTextField (20);
- pnlTemp.add (txtAddr);
- pnlMain.add (pnlTemp);
- // Create the layer for the main panel using our custom layerUI.
- return new JLayer<JPanel> (pnlMain, layerUI);
- }
- private static void createAndShowUI ()
- {
- JFrame frame = new JFrame ("Branded UI");
- frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
- frame.add (createLayer ());
- frame.pack ();
- frame.setLocationRelativeTo (null);
- frame.setVisible (true);
- }
- public static void main (String [] args)
- {
- Runnable r = new Runnable ()
- {
- public void run ()
- {
- createAndShowUI ();
- }
- };
- EventQueue.invokeLater (r);
- }
- }
上面的代碼渲染UI上的文本印記,我們通常使用印記提醒用戶使用的是試用軟件,印記文本是半透明的,以便背景可以全部顯示,我們不希望這個(gè)印記給用戶造成太大的干擾。
另一方面,我們希望用戶能注意到這個(gè)印記,讓他們下定決心購(gòu)買這款軟件,清單2中的代碼通過改變印記的顏色(改成淡紅色)來達(dá)到這個(gè)目的,當(dāng)鼠標(biāo)移到初始值是綠色的印記面板上時(shí),顏色就變成淡紅色。
#p#
事件檢測(cè)
JLayer和LayerUI結(jié)合起來可以檢測(cè)視圖任意區(qū)域上發(fā)生的事件(包括嵌套的子組件),這些類共同提供了4個(gè)方法來檢測(cè)事件。
· public void setLayerEventMask(long layerEventMask)
調(diào)用這個(gè)JLayer方法時(shí)必須使用位掩碼AWTEvent常量選擇它檢測(cè)到的事件類型,如:setLayerEventMask (AWTEvent.KEY_EVENT_MASK | AWTEvent.FOCUS_EVENT_MASK);可以檢測(cè)到按鍵和焦點(diǎn)改變事件。
◆public void installUI(JComponent c)
這個(gè)LayerUI方法通常放在setLayerEventMask()方法之前,這個(gè)方法類的代碼首先調(diào)用超類方法(super.installUI (c);),然后是引用JLayer的JComponent參數(shù),最后使用setLayerEventMask(): ((JLayer) c).setLayerEventMask(AWTEvent.KEY_EVENT_MASK);返回的結(jié)果。
◆public void uninstallUI(JComponent c)
這個(gè)LayerUI方法放在沒有參數(shù)的setLayerEventMask()方法后,這個(gè)方法內(nèi)的代碼首先調(diào)用超類方法(super.uninstallUI (c);),然后是引用JLayer的JComponent參數(shù),最后使用setLayerEventMask(): ((JLayer) c).setLayerEventMask(0);返回的結(jié)果。
◆public void eventDispatched(AWTEvent e, Jlayer l)
只要前面注冊(cè)的事件發(fā)生了,就會(huì)調(diào)用這個(gè)LayerUI方法,在這個(gè)方法中插入的代碼負(fù)責(zé)響應(yīng)事件,并恰當(dāng)?shù)馗聦?,更新了不同的繪制屬性(如顏色)后,通過傳遞給這個(gè)方法的JLayer參數(shù)調(diào)用repaint()方法重新繪制視圖。
在清單2中,LayerUI的installUI()方法調(diào)用setLayerEventMask(AWTEvent.MOUSE_MOTION_EVENT_MASK)檢測(cè)鼠標(biāo)移動(dòng)事件,它又調(diào)用eventDispatched()方法返回結(jié)果。
這個(gè)方法首先調(diào)用javax.swing.SwingUtilities類的convertPoint()方法確定鼠標(biāo)移動(dòng)事件相對(duì)于層的坐標(biāo)位置。
接下來這個(gè)方法通過檢查它的坐標(biāo)是否落在圍繞UI中心的一個(gè)矩形區(qū)域內(nèi),檢測(cè)鼠標(biāo)指針是否移到印記文本上方,如果坐標(biāo)剛好落在這個(gè)矩形區(qū)域內(nèi),印記文本的顏色就變?yōu)榈t色,除此以外,印記文本的顏色就恢復(fù)為藍(lán)色。
圖2顯示了鼠標(biāo)移到印記文本上方前后的顏色變化。
圖2 鼠標(biāo)指針移到文本上方時(shí),重新繪制文本顏色給用戶一個(gè)不刺眼的提示
小結(jié)
JLayer對(duì)自定義繪制和事件檢測(cè)的支持讓你可以改進(jìn)UI的各個(gè)組件,你可以將這個(gè)Swing組件和半透明及任意形狀窗口特性結(jié)合起來使用,讓你可以設(shè)計(jì)出更有趣的用戶界面。
【編輯推薦】