写在前面
对于平常的绘图来讲,java绘图机制无需了解太多,但是朦胧容易产生错误,绘图操作包括了整个GUI的显示问题,遂花了一些时间来学习、整理下,本文主要基于[1][2]以及自我实践加工而成(如有错误,请纠正我)。这里所讲的很多技术可能已经过时了,你可以略过这些部分,但是像下面关键概念以及绘图指导意见部分还是应该掌握。
java GUI开发库,经过最初的AWT,发展到Swing,SWT以及现在的Jface。这些GUI框架各有不同。
在AWT在JDK 1.0时引入,此时系统中只有重量级组件(heavyweight component)(见下文解释),这种组件采用"对等机制",对本地系统依赖性很大。 同时,采用这种方式时要处理诸如脏区脏检测,计算裁剪区和Z次序(damage detection, clip calculation, and z-ordering.)。
在JDK 1.1 中轻量级组件(lightweight component)被引入,AWT需要在共享的java 代码中实现它们的绘制过程。对于轻量级和重量级组件,AWT处理略有不同。
在JDK 1.1 之后引入了Swing,Swing绘图机制和AWT类似并且依赖于AWT,但是Swing在绘图机制上也与AWT有差别。AWT采用"对等机制",调用本地操作系统的控件。Swing只为诸如窗口和框架之类的顶层组件调用操作系统控件。大部分组件都是使用纯Java代码来模拟的。Swing也引入了新的API来简化绘图工作。
Swing与AWT的依赖关系可以参见下图(来自[3]) :
java类库中Swing与AWT类间关系如下图所示:
以下介绍以下java GUI编程中基本的概念,它们需要你了解,如果你未曾熟悉的话。
在java GUI中,任何想要显示的组件必须存在于一个窗口包含阶层(containment hierarchy)中。这个概念类似于树(或者像文件目录系统一样的递归结构),每个组件类似于树中的中间节点和叶子节点,因此这里我们可以把它理解为包含关系树。这个树的树根必须是顶层窗口,在Swing中顶层窗口包括:JFrame, JDialog, 和 JApplet.
每一个组件只能被包含一次,如果之前已经加入到一个组件当中了,那么之后加到其他组件时,它就处在这个最近添加到的位置里面。
比如我们在JFrame上面放置了一个文本框和按钮,那么这个包含关系树如下图所示:
注意这里JFrame是顶层窗口,实际上每个顶层窗口都会默认的包含一个content pane来容纳其中的组件。
java语言中,有两种组件,一种是重量级(heavyweight)组件,一种是轻量级(lightweight)组件。
1)重量级组件与它的本地屏幕资源相关联,通常称之为peer(java源码中很多时候,出现对peer的判断).来自于java.awt包里面的组件,像Button 、Label都是重量级组件。
一些重量级的第三方包,像
也变得越来越流行起来(参考自[4])。
2)轻量级组件,没有它自身的屏幕资源,因此更"轻"。一个轻量级组件依赖于它在包含关系阶层中的祖先窗口的屏幕资源,例如JFrame的。
java.awt包中的Component 和Container子类以及所有的Swing组件都是轻量级的。
3)在 java.awt.Component库中,包含了一个用于判断组件到底是不是轻量级的方法:
//true if this component has a lightweight peer; false if it has a native peer or no peer
public boolean isLightweight() {
return getPeer() instanceof LightweightPeer;
}
注意,isLightweight()函数在组件不可显示时,是不能确定组件是不是轻量级的。
后面我们将会看到,绘图机制在轻量级与重量级组件处理上的不同。
1) 是谁在什么时候调用我们的事件处理方法?
我们在编写GUI中响应代码时,可能会这样写:
JButton btn = new JButton("Submit");
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// do something
}
});
那么到底是谁会调用这个方法呢?又是什么时候调用呢?
实际上在java 处理GUI任务时会生成一个唯一线程从事件队列事件中抽取事件,并转发给应用程序定义的事件处理器。这个单线程就是所谓的事件派发线程EDT,这个概念可参见下图(来自[3]):
2)为什么是一个单线程在执行GUI任务?
多线程编程虽然可以带来性能上的提升,充分利用系统空闲资源,提高用户响应性;但是维护多线程的安全性和可靠性,缺相当困难。不仅仅是java 的GUI框架采用单线程的,还有像Qt, NextStep, MacOS Cocoa, X Windows等等都是单线程化的(参考[5])。
由于大多数的Swing组件的方法并不是线程安全的,因此通过使用单线程来保证它的线程安全,因为多线程的GUI会尤其受死锁的影响,维护这些线程安全代价太大。
单线程的事件派发线程,让GUI任务像一个一个短任务在GUI线程中串行执行。
这里并不打算深入讨论线程安全问题,只给出我们在编写事件处理代码时的注意事项:
更多关于GUI并发的问题,可参见java官网教程。
在GUI程序中,窗口或者组件的重绘请求有两个来源,一个是系统触发的,另一个是应用程序自身触发的。
1)系统触发的重绘请求
2)程序触发的重绘请求
在程序触发的重绘中,由于组件的内部状态改变了因而决定更新自己的内容,比如,一个按钮监听到鼠标按下了,它会调整为按下状态的视觉。
对于系统触发和程序触发的重绘,处理有一些差别,稍后将会见到。
不管一个绘画请求是由系统触发的还是程序触发的,AWT中使用回调机制来处理绘画,并且这种机制对轻量级组件和重量级组件相同。这意味着我们需要把渲染组件的代码写在一个可覆盖可的特殊的方法中,那么绘图工具箱就会在绘制时触发这些操作代码。在AWT中这个要覆盖的方法是:
public void paint(Graphics g);
Graphics是图形上下文对象(类似于MFC里面的Device context,用来完成GDI绘制),用来完成具体的绘制。当AWT绘图方法被触发时,系统已经给这个对象,预置了很多属性,包括绘制组件的颜色、字体、坐标转换和适合组件重绘大小的裁剪区。
下面我们给出AWT中覆盖paint方法,绘制一个Karel机器人(MIT Karel 与java 公开课中的机器人形象),我给出简洁版本:
例1: Karel in AWT
package painting; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Canvas; import java.awt.Color; import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; /** * this program demonstrate painting in AWT * @author wangdq * */ public class PaintDemo1 { public static void main(String[] args) { createAndShowGUI(); } private static void createAndShowGUI() { // TODO Auto-generated method stub Frame frame = new Frame("AWT paint demo"); frame.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.add(new KarelCanvas(),BorderLayout.CENTER); frame.pack(); frame.setVisible(true); } } /** * the canvas to make your custom draw * @author wangdq */ class KarelCanvas extends Canvas { public KarelCanvas() { this.setBackground(Color.WHITE); } @Override public void paint(Graphics g) { drawKarel(g); } @Override public Dimension getPreferredSize() { return new Dimension(500,400); } /** * draw the robot karel * here we haven't do precise calculate * the code style is not encouraged * you should encapsulate the code in a class named Karelxxx */ private void drawKarel(Graphics g) { Graphics2D g2d= (Graphics2D) g; g2d.setPaint(Color.BLACK); g2d.setStroke(new BasicStroke(3)); int headWid = this.getWidth()/3; int headHt = this.getHeight()/2; int headX = this.getWidth()/5; int headY = this.getHeight()/4; int offset = headHt/5; //draw inner rectangle g2d.drawRect(headX+headWid/4, headY+headHt/4, headWid/2, headHt/2); //draw outter rectangle int[] xPt = {headX,headX,headX+offset,headX+headWid,headX+headWid,headX+headWid-offset}; int[] yPt = {headY,headY+headHt-offset,headY+headHt,headY+headHt,headY+offset,headY}; for(int i = 0;i < xPt.length-1;i++) g2d.drawLine(xPt[i], yPt[i], xPt[i+1], yPt[i+1]); g2d.drawLine(xPt[xPt.length-1], yPt[xPt.length-1], xPt[0], yPt[0]); //draw the bottom line g2d.drawLine(xPt[2]+offset/2, yPt[2]-offset/2, xPt[3]-offset/2, yPt[2]-offset/2); //draw legs g2d.fillRect(headX-offset, yPt[1]-offset, offset, offset/2); g2d.fillRect(headX-offset, yPt[1]-offset, offset/2, offset); g2d.fillRect((xPt[2]+xPt[3])/2, yPt[2], offset/2, offset); g2d.fillRect((xPt[2]+xPt[3])/2, yPt[2]+offset/2, offset, offset/2); } private static final long serialVersionUID = 1L; }
程序运行效果如下图所示:
对于paint方法书写要注意两点:
repaint方法,用于异步请求重绘操作;这个方法有四个重载版本,如下:
public void repaint() //整个区域重绘
public void repaint(long tm) //指定重绘时间
public void repaint(int x, int y, int width, int height)
public void repaint(long tm, int x, int y, int width, int height)
分别对应不同情况.
注意: 我们在请求组件重绘时,一定要记住,我们尽量使用带参数版本来缩小重绘区域,减轻系统压力。
对于系统触发的请求:
程序触发的请求:
对于大多数组件来说,默认情况下最终调用paint()方法,因此,我们也不用细加区分系统触发和程序触发的不同点,但是注意一点就是重量级组件可以利用update()方法实现增量作图(incremental painting),这是一项优点(轻量级组件不能实现增量作图)。增量作图,我通过实验,感觉就是不用擦除原来的背景,继续往组件上添加新内容,可以在一定程度上,减少闪烁。
虽然默认下,最终都是调用paint()方法来绘制组件;但是 AWT框架中轻量级组件的实现代码全部由java实现,因此与重量级组件还是有区别的。
轻量级组件的绘制依赖与包含关系阶层中的重量级祖先组件,当这个祖先组件被通知绘制时,它将把绘制请求转化为绘制自身上任何可见的子孙组件,这个方法是由java.awt.Container's paint() 方法来完成的,因此任何Container的子类,在覆盖paint方法时一定要记得调用super.paint()来保证(这是错误的一个来源),它上面的轻量级子孙组件都被绘制到了。
写代码时可以这样架构:
public class MyContainer extends Container {
public void paint(Graphics g) {
// 先绘制自身内容
// 然后确保轻量级子组件被绘制
super.paint(g);
}
}
这意味着轻量级组件的update和paint没有什么区别,因此不能用于增量作图。
3.4 轻量级组件的透明特性
轻量级组件借用了重量级组件的屏幕资源,它们支持透明特性。因为,轻量级组件是从后面向前面绘制的,如果它们的某些像素没有绘制,那么这部分下面的组件就会“穿透”显示出来,这也是轻量级组件的update()方法默认没有清除背景的原因了。
轻量级组件的透明特性,可以参看例2: Moon in Nightsky ,来体会。这个例子中,月亮组件只绘制了一半的区域,后面的夜色背景透过月亮组件未绘制的区域显示出来,表现了轻量级组件的透明特性。
例2: Moon in Nightsky
package painting; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Frame; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; /** * this program demonstrate Transparency in lightweight component * @author wangdq * */ public class PaintDemo3 { public static void main(String[] args) { createAndShowGUI(); } private static void createAndShowGUI() { // TODO Auto-generated method stub Frame frame = new Frame("Lightweight Transparency demo"); frame.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e) { System.exit(0); } }); NightSky sky = new NightSky(); sky.add(new Moon()); frame.add(sky,BorderLayout.CENTER); frame.pack(); frame.setVisible(true); } } /** * Night sky container * lightweight component * */ class NightSky extends Container { public NightSky() { this.setBackground(Color.BLACK.brighter()); this.setLayout(new FlowLayout()); } @Override public Dimension getPreferredSize() { return new Dimension(400,400); } @Override public void paint(Graphics g) { //draw background g.setColor(this.getBackground()); g.fillRect(0, 0, this.getWidth(), this.getHeight()); //draw children super.paint(g); } private static final long serialVersionUID = 1L; } /** * Moon component * lightweight component */ class Moon extends Component { @Override public Dimension getPreferredSize() { return new Dimension(200,200); } @Override public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; GradientPaint gradient = new GradientPaint(0,0,Color.WHITE, 150,150,Color.YELLOW.darker()); g2d.setPaint(gradient); //paint part of the component g2d.fillOval(0, 0, this.getWidth()/2, this.getWidth()/2); } private static final long serialVersionUID = 1L; }
前面的这些分析,有些帮助,但是不够具体实用,还是看看这些指导意见。
Swing以AWT的基本绘图模型为基础,拓展了它从而让绘图的性能最好,并提高绘图灵活性。像AWT一样,Swing也采用paint回调机制和repaint来触发更新。
另外,Swing还增加了诸如,内置双缓冲作图,UI代理,以及用于自定义绘图机制的RepaintManager API .
4.1 Swing中绘图代码该放在哪里?
Swing中当组件要绘制时,依然需要回调机制,但是不是调用paint()方法,而是把paint()方法分解成三个方法:
protected void paintComponent(Graphics g)
protected void paintBorder(Graphics g)
protected void paintChildren(Graphics g)
这样的分解增加了灵活性,解决了诸如AWT中覆盖paint()方法漏掉super.paint()的风险。
Swing程序应该覆盖paintComponent()来实现图形绘制,而不是覆盖paint()方法;另外paintBorder和paintChidren方法通常也不应该覆盖。
这里我们给出Swing组件中绘制karel机器人的简洁代码,与上文AWT中代码作参照:
例3: Karel in Swing
package painting; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; /** * this program demonstrate painting in Swing * @author wangdq * */ public class PaintDemo2 { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub createAndShowGUI(); } }); } private static void createAndShowGUI() { // TODO Auto-generated method stub JFrame frame = new JFrame("Swing paint demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new KarelComponent(),BorderLayout.CENTER); frame.pack(); frame.setVisible(true); } } /** * the component to make your custom draw * @author wangdq * */ class KarelComponent extends JComponent { public KarelComponent() { this.setBackground(Color.WHITE); this.setBorder(BorderFactory.createLineBorder(Color.BLUE, 5)); this.setLayout(new BorderLayout()); this.add(new JLabel("Hello,Karel!",JLabel.CENTER),BorderLayout.SOUTH); } @Override public void paintComponent(Graphics g) { // Let UI delegate paint first // (including background filling, if I'm opaque) super.paintComponent(g); //paint my contents next drawKarel(g); } /** * we should not override paint() directly in swing * or we will not see the blue border(paintBorder not called) * and label(paintchild not called) @Override public void paint(Graphics g) { drawKarel(g); } */ @Override public Dimension getPreferredSize() { return new Dimension(500,400); } /** * draw the robot karel * here we haven't do precise calculate * the code style is not encouraged * you should encapsulate the code in a class named Karelxxx */ private void drawKarel(Graphics g) { Graphics2D g2d= (Graphics2D) g; g2d.setPaint(Color.BLACK); g2d.setStroke(new BasicStroke(3)); int headWid = this.getWidth()/3; int headHt = this.getHeight()/2; int headX = this.getWidth()/5; int headY = this.getHeight()/4; int offset = headHt/5; //draw inner rectangle g2d.drawRect(headX+headWid/4, headY+headHt/4, headWid/2, headHt/2); //draw outter rectangle int[] xPt = {headX,headX,headX+offset,headX+headWid,headX+headWid,headX+headWid-offset}; int[] yPt = {headY,headY+headHt-offset,headY+headHt,headY+headHt,headY+offset,headY}; for(int i = 0;i < xPt.length-1;i++) g2d.drawLine(xPt[i], yPt[i], xPt[i+1], yPt[i+1]); g2d.drawLine(xPt[xPt.length-1], yPt[xPt.length-1], xPt[0], yPt[0]); //draw the bottom line g2d.drawLine(xPt[2]+offset/2, yPt[2]-offset/2, xPt[3]-offset/2, yPt[2]-offset/2); //draw legs g2d.fillRect(headX-offset, yPt[1]-offset, offset, offset/2); g2d.fillRect(headX-offset, yPt[1]-offset, offset/2, offset); g2d.fillRect((xPt[2]+xPt[3])/2, yPt[2], offset/2, offset); g2d.fillRect((xPt[2]+xPt[3])/2, yPt[2]+offset/2, offset, offset/2); } private static final long serialVersionUID = 1L; }
程序运行结果如下图所示:
注意,在代码中我们注释了paint()覆盖的代码,如果直接覆盖paint()方法,我们将看不到蓝色边框和标签子组件。
4.2 UI 代理如何实现组件风格和外观个性化
Swing提供了将组件绘制的过程委托给代理的方式来支持组件的外观和风格,使组件的外观和风格变得可灵活变换;这意味着组件绘制过程变成:
可以参考java源码(免责声明:代码归属Oracle及其附属组织所有,这里只是学习):
javax.swing.JComponent.paintComponent
protected void paintComponent(Graphics g) { if (ui != null) { Graphics scratchGraphics = (g == null) ? null : g.create(); try { ui.update(scratchGraphics, this); } finally { scratchGraphics.dispose(); } } }
public void update(Graphics g, JComponent c) {
if (c.isOpaque()) {
g.setColor(c.getBackground());
g.fillRect(0, 0, c.getWidth(),c.getHeight());
}
paint(g, c);
}
ComponentUI带来一个问题是,从有默认代理的类(JComponent类有,因此从其派生类应该注意)继承而来时paintComponent方法在覆盖时需要首先调用super.paintComponent(),让UI来处理绘制,可以参见例2代码中的书写方法。
如果你没有加上super.paintComponent()或者跳过代理的处理,而该组件又是不透明的话,则需要注意自己加上处理组件背景的代码。
为了支持RepaintManager ,提高性能Swing绘图处理过程更加复杂。
主要有两种方式:
1)绘图请求源自第一个重量级祖先组件,比如JFrame, JDialog, JWindow, or JApplet。
处理方法:
2)从javax.swing.JComponent拓展的组件的repaint()方法发起的绘制请求.
处理方法如下:
在EDT上执行的这个任务,引发组件的RepaintManager在改组件上调用paintImmediately(),这个方法采取以下步骤:
这可以看出,Swing组件上update方法从来没有被调用。
注意,尽管repaint()导致了paintImmediately()方法的调用,但是这个函数并不是回调函数,用户代码不应该写在它里面,并且如果没有什么特别理由,也不要覆盖这个方法。
使用这个方法的原则就是,仅仅通过EDT线程调用它,除非是为了实现实时编程(实时编程可以参考相关书籍)。
注意,java现在的Swing组件中所有组件默认已经内置了双缓冲功能,如果你不想了解这个话题,可以跳过。当然,也许,其他语言编程时如果未内置双缓冲或者双缓冲不成熟时,了解机制还是有帮助的。
双缓冲绘图是一个老话题,为了消除直接作图的闪烁(flicker),使用双缓冲机制,在内存中也即屏幕外可绘对象上先把图形绘制好,然后再依次拷贝到原绘图区域,可以降低屏幕闪烁。双缓冲绘图机制可以理解为下图所示(图来自[6]):
为了帮助理解双缓冲,这里还是再做一个实验(代码部分参卡自[7],[8]),如例4 : Double-buffer in AWT 所示:
例4 : Double-buffer in AWT
package painting;
import java.awt.*;
import java.awt.event.*;
/**
* This program try to demonstrate double-buffer usage in drawing
* move your mouse to observe screen flicker in the two canvas
* @author wangdq
*/
public class PaintDemo4 {
public static void main(String[] args) {
createAndShowGUI();
}
private static void createAndShowGUI() {
Frame frame = new Frame("Double-buffer in AWT demo");
frame.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.add(new CanvasEx(false,"Not Using Double-Buffer",
Color.BLUE),BorderLayout.CENTER);
frame.add(new CanvasEx(true,"Using Double-Buffer",
Color.ORANGE),BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
/**
* the canvas to show content
* here we try to simplify code ,the code style does not encouraged
*/
class CanvasEx extends Canvas {
public CanvasEx(boolean dBufEnabled,String text,Color bgClr) {
this.doubleBufferEnabled = dBufEnabled;
this.text = text;
this.setBackground(bgClr);
this.addMouseMotionListener(new MouseMotionListener(){
@Override
public void mouseDragged(MouseEvent e) {
}
@Override
public void mouseMoved(MouseEvent e) {
pos = (Point)e.getPoint().clone();
repaint();
}
});
pos = new Point();
}
@Override
/**
* override it to avoid clearing the background by default
*/
public void update(Graphics g) {
paint(g);
}
@Override
public void paint(Graphics g) {
if(doubleBufferEnabled) {
// checks the buffersize with the current panelsize
// or initialises the image with the first paint
if(bufferWidth!=getSize().width ||
bufferHeight!=getSize().height ||
bufferImage==null || bufferGraphics==null)
resetBuffer();
drawContent(bufferGraphics);
g.drawImage(bufferImage, 0, 0, this.getWidth(), this.getHeight(), this);
} else {
drawContent(g);
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(300,200);
}
//we pass the off-screen graphic to it using double buffer
//switch to the normal paint by passing the screen graphics
private void drawContent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setPaint(this.getBackground());
g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
g2d.setPaint(Color.RED);
g2d.drawString(text,50,50);
g2d.setPaint(Color.DARK_GRAY);
g2d.fillOval(pos.x,pos.y,50,50);
}
private void resetBuffer() {
// always keep track of the image size
bufferWidth=getSize().width;
bufferHeight=getSize().height;
// clean up the previous image
if(bufferGraphics!=null){
bufferGraphics.dispose();
bufferGraphics=null;
}
if(bufferImage!=null){
bufferImage.flush();
bufferImage=null;
}
System.gc();
// create the new image with the size of the panel
bufferImage=createImage(bufferWidth,bufferHeight);
bufferGraphics=bufferImage.getGraphics();
}
private Point pos;
private Image bufferImage;//off-screen drawable image
private int bufferWidth;
private int bufferHeight;
private Graphics bufferGraphics;//off-screen graphics
private boolean doubleBufferEnabled;
private String text;
private static final long serialVersionUID = 1L;
}
程序运行如下图所示:
测试时,移动鼠标,观察屏幕闪烁即可得出结论。
在Swing中,默认所有组件都具有双缓冲功能,所以代码就像书写这个没有使用屏幕外绘图对象时的代码一样简洁。可以通过javax.swing.JComponent:类的
public boolean isDoubleBuffered()
public void setDoubleBuffered(boolean o)
方法来设置和获取当前组件是否使用双缓冲。注意,如果祖先组件设置为使用双缓冲则它里面的组件将都会使用双缓冲,而忽略子组件的设置。
Swing为了提高绘图性能,引入了不透明度(Opacity),优化作图选项(OptimizedDrawingEnabled)以及重绘管理器(RepaintManager)等特性。
javax.swing.JComponent提供了
public boolean isOpaque()
public void setOpaque(boolean o)来设置不透明度;不透明度指示组件是否保证在它的矩形边界内的像素都会绘制。当这个属性为true时,将会绘制组件边界内全部像素,否则则不保证全部绘制。这个值默认是由外观UI代理设定的,对于大多数组件为true.
关于这个属性注意三点:
这个选项用来解决组件重叠的棘手问题。如果组件被同层次的组件或者与祖先组件不相干的组件重叠的话,绘制一个组件需要很多的遍历来确保组件被正确的绘制。
为了减少这种遍历,Swing为组件添加了这个只读属性,可以通过 javax.swing.JComponent的
public boolean isOptimizedDrawingEnabled() 来判断是否可以优化作图。
该方法返回true表明中间组件没有重叠,否则组件不能确保中间组件是否重叠。
通过检查这个属性,Swing框架可以缩小在绘图时需要搜索重叠组件的范围。
这个属性是只读的,子类更改它的唯一方法是覆盖这个方法并返回想要的值.所有标准Swing组件返回true,除了JLayeredPane, JDesktopPane,和JViewPort.
重绘管理器的提出是为了提升Swing包含关系阶层的绘图性能和实现Swing的验证机制(revalidation mechanism )。它通过截获所有Swing组件的重绘请求(这些请求将不再由AWT处理)和维护那些指示哪里需要更新的自身状态(俗称为"脏区""dirty regions")来实现重绘机制,最后通过 invokeLater() 在EDT上来处理截获的请求。
用户可以查看、替换、实现自己的重绘管理器。
重绘管理器对双缓冲机制有着全局控制,使用
public void setDoubleBufferingEnabled(boolean aFlag)
public boolean isDoubleBufferingEnabled()
来设置启用或者关闭双缓冲,这个属性默认为true。
如果不想默认使用双缓冲的话可以通过:
RepaintManager.currentManager(mycomponent).
setDoubleBufferingEnabled(false);来关闭双缓冲,
注意mycomponent时哪个具体组件不重要。
RepaintManager在实践中暂时还未用上,关于重绘管理器更高级的话题这里就不做讨论。
Swing绘图处理相对AWT更加灵活,性能更好,但是使用起来注意事项也更多,要获得更好总是要付出代价的,我们来看:
[1]: http://www.oracle.com Painting in AWT and Swing
[2] csdn jxsfreedom的专栏 JAVA 画图机制
[3]: 《Filthy Rich Clients》 一书
[4]: http://www.oracle.com Mixing Heavyweight and Lightweight Components
[5]: 电子工业出版社 《java并发编程实践》
[6] : 博客园 LinJie博客 C++零食:WTL中使用双缓冲避免闪烁
[7] : codeproject博客Double buffer in standard Java AWT
[8] :http://www.realapplets.com Chapter 4 - Advanced Topics1. Double-Buffering.
GUI绘图,java 2D绘图等问题,有些方面是比较复杂且浪费精力的,建议除了从事图形界面编程、视觉处理的专业外,对绘图机制有个大致了解能编写简单应用即可,不要花费过多精力研究这方面的技术,遇到问题需要时再去翻字典,因为还有更重要的技术在等待我们去学习。