我们很多用Java中的g.drawImage()方法导入图像时,如果我们将当前窗口转变成非当前窗口状态,再从非当前窗口恢复到当前窗口状态,有时,某些绘制好的图像会消失,除非我们重新刷新窗口,显示才会恢复正常。此外,当我们移动窗口或者其他的窗口在上移动的时候,图像会有些闪烁。这是怎么一回事呢?这就要涉及到Canvas中的paint方法的绘图机制了。产生这种现象的主要原因是:
1、由于在显示所绘制的图像时,调用了repaint方法。repaint方法被调用时,需要清除整个背景,然后才调用paint方法显示画面。这样,在清除背景和绘制图像的短暂时间间隔内被用户看见的就是闪烁。
2、由于paint()方法需要进行复杂的计算,图像中包含着多个图形,不同图形的复杂程度及其所需要的绘制时间不同,因此,图像中的各个像素值不能同时产生,使得图形的生成频率低于显示器的刷新频率,从而造成闪烁。
下面两种方法可以明显地消除或减弱闪烁:
1、重载update方法
当AWT接收到Canvas重新绘制的请求时,调用Canvas的update方法。默认情况下,update方法清除Canvas的背景,然后调用paint方法。重载update方法,就可以将以前在paint方法中的绘图代码包含在update方法中,从而避免每次重新绘制时将整个区域清除。
2、双缓冲技术
双缓冲技术在很多动画中被采用。主要原理是创建一幅BufferedImage图像,将每一帧画入图像,然后调用drawImage方法,将整个BufferedImage图像一次画到屏幕上去。这种方法的优点在于大部分绘制是在BufferedImage进行的。将BufferedImage绘制的图像一次绘制到屏幕上。首先通过调用new BufferedImage方法生成合适的缓冲区,然后获得在缓冲区的绘图环境(即Graphics类对象)。
综上所述,在实际操作中,我们导入图像的思路是:
不采用paint方法直接刷新,而是重写update方法,生成一个图像的缓冲区,获得该缓冲区的绘图环境后,将该绘图环境读入内存。然后调用paint方法在图像的缓冲区上作画。当所有的图像绘制工作完成后,最后将缓冲区的内容一次性的写入画板并在窗口中直接显示出来。由于update方法本身会清除背景,而重写后要自己代码清除背景,否则会在原图像未被清除的情况下继续作画,形不成动画。
例子代码如下:
Image offScreenImage=null;
public void update(Graphics g) { //双缓冲技术
if(offScreenImage==null){
offScreenImage=this.createImage(GAME_WIDTH,GAME_HEIGHT); }
Graphics goffScreen=offScreenImage.getGraphics();
Color c=goffScreen.getColor();
goffScreen.setColor(Color.GREEN);
goffScreen.fillRect(0, 0, GAME_WIDTH,GAME_HEIGHT);
goffScreen.setColor(c);
paint(goffScreen);
g.drawImage(offScreenImage, 0, 0,null);
}
当然此方法并不适应所有情况,对于调用repaint方法的程序,调用流程如下:
repaint()方法 | | V AWT线程--->paint()方法-->图形绘制 | | V
update()方法--->paint()方法--图形绘制
repaint方法这样规定:如果绘制的轻量级组件,则update方法不会被调用,直接由AWT线程调用paint方法重新绘制。
此时如果采用重写update方法则不会产生任何影响。我们应该采用的是重写repaint方法实现双缓冲来消除闪烁。
附:
轻量级组件 是用JAVA代码画出来的,这样具有平台移植性
重量级组件 是调用操作系统的函数画出来的组件,比如主窗体
一般来说尽量用轻量级的组件,这样对程序的移植性很好,一般javax.swing包里的组件大部分是轻量级的java.awt里面的是重量级的。
Swing是由100%纯Java实现的,Swing组件是用Java实现的轻量级( light-weight)组件,没有本地代码,不依赖操作系统的支持,这是它与AWT组件的最大区别。由于AWT组件通过与具体平台相关的对等类(Peer)实现,因此Swing比AWT组件具有更强的实用性。Swing在不同的平台上表现一致,并且有能力提供本地窗口系统不支持的其它特性。