12.5 Java7新增的Swing功能 Java7
提供的重大更新就包括了对Swing
的更新,对Swing
的更新除前面介绍的Nimbus
外观、改进的JColorchooser
组件之外,还有两个很有用的更新:JLayer 和创建不规则窗口 。下面将会详细介绍这两个知识点。
12.5.1 使用JLayer装饰组件 JLayer
的功能是在指定组件上额外地添加一个装饰层,开发者可以在这个装饰层上进行任意绘制(直接重写paint(Graphics g,JComponent c)
方法),这样就可以为指定组件添加任意装饰。
JLayer
一般总是要和LayerUI
一起使用,而LayerUI
用于被扩展,扩展LayerUI
时重写它的paint(Graphics g,JComponent c)
方法,在该方法中绘制的内容会对指定组件进行装饰。
实际上,使用JLayer
很简单,只要如下两行代码即可
1 2 3 4 LayerUI<JComponent> layerUI = new XxxLayerUI (); JLayer<JComponent> layer = new JLayer <JComponent>(panel, layerUI);
上面程序中的XxxLayerUI
就是开发者自己扩展的子类,这个子类会重写paint(Graphics g,JComponent c)
方法,重写该方法来完成“装饰层”的绘制。
上面第二行代码中的panel
组件就是被装饰的组件,接下来把layer
对象(layer
对象包含了被装饰对象和LayerUI
对象)添加到指定容器中即可
程序 蒙版效果 下面程序示范了使用JLayer
为窗口添加一层“蒙版”的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class FirstLayerUI extends LayerUI <JComponent> { private static final long serialVersionUID = 5993086002698144927L ; public void paint (Graphics g, JComponent c) { super .paint(g, c); Graphics2D g2 = (Graphics2D) g.create(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f )); g2.setPaint(new GradientPaint (0 , 0 , Color.RED, 0 , c.getHeight(), Color.BLUE)); g2.fillRect(0 , 0 , c.getWidth(), c.getHeight()); g2.dispose(); } }
程序 主类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class JLayerTest { public void init () { JFrame f = new JFrame ("JLayer测试" ); JPanel p = new JPanel (); ButtonGroup group = new ButtonGroup (); JRadioButton radioButton; p.add(radioButton = new JRadioButton ("网购购买" , true )); group.add(radioButton); p.add(radioButton = new JRadioButton ("书店购买" )); group.add(radioButton); p.add(radioButton = new JRadioButton ("图书馆借阅" )); group.add(radioButton); p.add(new JCheckBox ("疯狂Java讲义" )); p.add(new JCheckBox ("疯狂Android讲义" )); p.add(new JCheckBox ("疯狂Ajax讲义" )); p.add(new JCheckBox ("轻量级Java EE企业应用" )); JButton orderButton = new JButton ("投票" ); p.add(orderButton); LayerUI<JComponent> layerUI = new BlurLayerUI (); JLayer<JComponent> layer = new JLayer <JComponent>(p, layerUI); f.add(layer); f.setSize(300 , 170 ); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true ); } public static void main (String[] args) { new JLayerTest ().init(); } }
上面程序中开发了一个FirstLayerUI
,它扩展了LayerUI
,重写paint(graphics g, JComponent c)
方法时绘制了一个半透明的、与被装饰组件具有相同大小的矩形。接下来在main
方法中使用这个LayerUI
来装饰指定的JPanel
组件,并把JLayer
添加到JFrame
容器中,这就达到了对JPanel
进行包装的效果。
运行该程序,可以看到如图12.23所示的效果
由于开发者可以重写paint(graphics g, JComponent c)
方法,因此获得对被装饰层的全部控制权——想怎么绘制,就怎么绘制!因此开发者可以“随心所欲”地对指定组件进行装饰。
程序 模糊效果 例如,下面提供的LayerUI
则可以为被装饰组件增加“模糊”效果。程序如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class BlurLayerUI extends LayerUI <JComponent> { private static final long serialVersionUID = -2386080677384858056L ; private BufferedImage screenBlurImage; private BufferedImageOp operation; public BlurLayerUI () { float ninth = 1.0f / 9.0f ; float [] blurKernel = { ninth, ninth, ninth, ninth, ninth, ninth, ninth, ninth, ninth }; operation = new ConvolveOp (new Kernel (3 , 3 , blurKernel), ConvolveOp.EDGE_NO_OP, null ); } public void paint (Graphics g, JComponent c) { int w = c.getWidth(); int h = c.getHeight(); if (w == 0 || h == 0 ) return ; if (screenBlurImage == null || screenBlurImage.getWidth() != w || screenBlurImage.getHeight() != h) { screenBlurImage = new BufferedImage (w, h, BufferedImage.TYPE_INT_RGB); } Graphics2D ig2 = screenBlurImage.createGraphics(); ig2.setClip(g.getClip()); super .paint(ig2, c); ig2.dispose(); Graphics2D g2 = (Graphics2D) g; g2.drawImage(screenBlurImage, operation, 0 , 0 ); } }
上面程序扩展了LayerUI
,重写了paint(Graphics g,JComponent c)
方法,重写该方法时也是绘制了一个与被装饰组件具有相同大小的矩形,只是这种绘制添加了模糊效果。
将JLayerTestJava
中的②号粗体字代码改为使用BlurLayerUI
,再次运行该程序,将可以看到如图12.24所示的“毛玻璃”窗口
给LayerUI增加事件机制 除此之外,开发者自定义的LayerUI
还可以增加事件机制,这种事件机制能让装饰层响应用户动作,随着用户动作动态地改变LayerUI
上的绘制效果。比如下面的LayerUI
示例。
程序 探照灯效果 程序通过响应鼠标事件可以在窗口上增加“探照灯”效果。程序如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class SpotlightLayerUI extends LayerUI <JComponent> { private static final long serialVersionUID = -5931810232610714874L ; private boolean active; private int cx, cy; public void installUI (JComponent c) { super .installUI(c); JLayer layer = (JLayer) c; layer.setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); } public void uninstallUI (JComponent c) { JLayer layer = (JLayer) c; layer.setLayerEventMask(0 ); super .uninstallUI(c); } public void paint (Graphics g, JComponent c) { Graphics2D g2 = (Graphics2D) g.create(); super .paint(g2, c); if (active) { Point2D center = new Point2D .Float(cx, cy); float radius = 72 ; float [] dist = { 0.0f , 1.0f }; Color[] colors = { Color.YELLOW, Color.BLACK }; RadialGradientPaint p = new RadialGradientPaint (center, radius, dist, colors); g2.setPaint(p); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .6f )); g2.fillRect(0 , 0 , c.getWidth(), c.getHeight()); } g2.dispose(); } public void processMouseEvent (MouseEvent e, JLayer layer) { if (e.getID() == MouseEvent.MOUSE_ENTERED) active = true ; if (e.getID() == MouseEvent.MOUSE_EXITED) active = false ; layer.repaint(); } public void processMouseMotionEvent (MouseEvent e, JLayer layer) { Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), layer); cx = p.x; cy = p.y; layer.repaint(); } }
上面程序中重写了LayerUI
的installUI(JComponent c)
方法,重写该方法时控制该组件能响应鼠标事件和鼠标动作事件。 接下来程序
重写processMouseMotionEvent()
方法,该方法负责为LayerUI
上的鼠标事件提供响应:当鼠标在界面上移动时,程序会改变cx
、cy
的坐标值 重写paint(Graphics g, JComponent c)
方法时会在cx
、ey
对应的点绘制一个环形渐变,这就可以充当“探照灯”效果了。
将JLayerTestJava
中的②号粗体字代码改为使用SpotlightLayerUI
,再次运行该程序,即可看到如图12.25所示的效果。
绘制动画 既然可以让LayerUI
上的绘制效果响应鼠标动作,当然也可以在LayerUI
上绘制“动画”,所谓动画,就是通过定时器控制LayerUI
上绘制的图形动态地改变即可。
接下来重写的LayerUI
使用了Timer
来定时地改变LayerUI
上的绘制,程序绘制了一个旋转中的“齿轮”,这个旋转的齿轮可以提醒用户“程序正在处理中”
程序 转动的齿轮 下面程序重写LayerUl
时绘制了12条辐射状的线条,并通过Timer
来不断地改变这12条线条的排列角度,这样就可以形成“转动的齿轮”了。
程序提供的WaitingLayerUI
类代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 class WaitingLayerUI extends LayerUI <JComponent> { private static final long serialVersionUID = -443302447858652985L ; private boolean isRunning; private Timer timer; private int angle; public void paint (Graphics g, JComponent c) { super .paint(g, c); int w = c.getWidth(); int h = c.getHeight(); if (!isRunning) return ; Graphics2D g2 = (Graphics2D) g.create(); Composite urComposite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f )); g2.fillRect(0 , 0 , w, h); g2.setComposite(urComposite); int s = Math.min(w, h) / 5 ; int cx = w / 2 ; int cy = h / 2 ; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setStroke(new BasicStroke (s / 2 , BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g2.setPaint(Color.BLUE); g2.rotate(Math.PI * angle / 180 , cx, cy); for (int i = 0 ; i < 12 ; i++) { float scale = (11.0f - (float ) i) / 11.0f ; g2.drawLine(cx + s, cy, cx + s * 2 , cy); g2.rotate(-Math.PI / 6 , cx, cy); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, scale)); } g2.dispose(); } public void start () { if (isRunning) return ; isRunning = true ; timer = new Timer (100 , e -> { if (isRunning) { firePropertyChange("crazyitFlag" , 0 , 1 ); angle += 6 ; if (angle >= 360 ) angle = 0 ; } }); timer.start(); } public void stop () { isRunning = false ; firePropertyChange("crazyitFlag" , 0 , 1 ); timer.stop(); } public void applyPropertyChange (PropertyChangeEvent pce, JLayer layer) { if (pce.getPropertyName().equals("crazyitFlag" )) { layer.repaint(); } } }
程序 主类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class WaitingJLayerTest { public void init () { JFrame f = new JFrame ("转动的“齿轮”" ); JPanel p = new JPanel (); ButtonGroup group = new ButtonGroup (); JRadioButton radioButton; p.add(radioButton = new JRadioButton ("网购购买" , true )); group.add(radioButton); p.add(radioButton = new JRadioButton ("书店购买" )); group.add(radioButton); p.add(radioButton = new JRadioButton ("图书馆借阅" )); group.add(radioButton); p.add(new JCheckBox ("疯狂Java讲义" )); p.add(new JCheckBox ("疯狂Android讲义" )); p.add(new JCheckBox ("疯狂Ajax讲义" )); p.add(new JCheckBox ("轻量级Java EE企业应用" )); JButton orderButton = new JButton ("投票" ); p.add(orderButton); final WaitingLayerUI layerUI = new WaitingLayerUI (); JLayer<JComponent> layer = new JLayer <JComponent>(p, layerUI); final Timer stopper = new Timer (4000 , ae -> layerUI.stop()); stopper.setRepeats(false ); orderButton.addActionListener(ae -> { layerUI.start(); if (!stopper.isRunning()) { stopper.start(); } }); f.add(layer); f.setSize(300 , 170 ); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true ); } public static void main (String[] args) { new WaitingJLayerTest ().init(); } }
上面程序中的①号粗体字代码定义了一个angle
变量,它负责控制12条线条的旋转角度。程序使用Timer
定时地改变angle
变量的值(每隔0.1秒angle
加6),如③号粗体字代码所示。控制了angle
角度之后,程序根据该angle
角度绘制12条线条,如②号粗体字代码所示。
提供了WaitingLayerUI
之后,接下来使用该WaitingLayerUI
与使用前面的UI
没有任何区别。不过程序需要通过特定事件来显示WaitingLayerUI
的绘制(就是调用它的start
方法),下面程序为按钮添加了事件监听器:当用户单击该按钮时,程序会调用WaitingLayerUI
对象的start
方法。
1 2 3 4 5 6 7 8 orderButton.addActionListener(ae -> { layerUI.start(); if (!stopper.isRunning()) { stopper.start(); } });
除此之外,上面代码中还用到了stopper
计时器,它会控制在一段时间(比如4秒)之后停止绘制WaitingLayerUI
,因此程序还通过如下代码进行控制
1 2 3 4 final Timer stopper = new Timer (4000 , ae -> layerUI.stop());stopper.setRepeats(false );
再次运行该程序,可以看到如图12.26所示的“动画装饰”效果。
通过上面几个例子可以看出,Swing
提供的JLayer
为窗口美化提供了无限可能性。只要你想做的,比如希望用户完成输入之后,立即在后面显示一个简单的提示按钮(钩表示输入正确,叉表示输入错误)……都可以通过JLayer
绘制。