标准 Java AWT 中的双缓冲






4.66/5 (19投票s)
2002年4月17日
3分钟阅读

138957

440
使用标准 Java AWT 创建双缓冲基类。
引言
你是否曾经创建一个应用程序,在你的窗口中显示动画?你可能已经做过,如果你还没有,你可能有一天会。编码非常简单:你获取一个 Panel
,并向其添加一个线程,该线程每 x 毫秒调用 repaint
方法。完成了吗?
不,还没有完全结束。因为,如果你就这样放着它,屏幕会闪烁,你的动画最终会被部分绘制,或者根本没有绘制。
解决这个问题的方法是臭名昭著的双缓冲。
AWT 与 SWING
在 Swing 框架中,你可以通过在你的类中将此属性设置为 true 来添加双缓冲。Swing 正在为你执行所有需要的任务。它们在幕后,你只需要知道它已经完成。闪烁消失了。谢谢 Sun 先生。
但是如果你使用标准的 AWT 呢?Swing 框架相当笨重,并且仍然有一些令人讨厌的错误,因此,在较旧的机器上,你更有可能使用标准的 AWT。但是,这里没有双缓冲,不是吗?
我已经研究这个问题一段时间了,这是我想出的解决方案。这是一个解决方案,而不是唯一的解决方案。我也看过其他的,但它们或多或少都具有相同的组件。
DoubleBuffer 类
由于我们是优秀的 Java 程序员,我们为此创建一个基类,以便我们可以随时实现双缓冲功能。你可以在类 Component
、Container
、Canvas
和 Panel
之间进行选择来扩展,但我建议你选择 Panel
类。如果你选择另一个,你稍后可能会遇到问题,某些事件侦听器将无法正确触发。
import java.awt.*;
public class DoubleBuffer extends Panel{
public DoubleBuffer(){
super();
}
}
消除闪烁
是什么导致了闪烁?好吧,如果你调用 repaint
方法,你实际上是在告诉 VM 尽快重绘此组件。你无法知道何时(但你可以设置一个初始延迟)。当 VM 有时间执行绘画任务时,它会为你调用 update
方法。这里已经存在第一个问题。update
方法为你清除 panel
,因此你可以对其进行绘制,而无需担心背景。这就是导致闪烁的原因。它清除 panel
,在窗口中显示,绘制 panel
,然后再次显示。因此,每次绘制实际上都有两个作业,清除和绘制。
我们将弃用它。
public void update(Graphics g){
paint(g);
}
public void paint(Graphics g){
}
现在,背景不再被清除,因此闪烁消失了。如果你使用它来通过具有相同尺寸的一些图像进行动画处理,那么你已经完成了,因为你无需清除上一个图像。
但是如果你必须清除上一个绘制呢?
缓冲区图像
双缓冲背后的想法是,你首先将所有内容绘制到屏幕外图像,并在准备就绪后,以一个绘制作业将其绘制到屏幕上。为此,我们需要一个 bufferimage
及其兄弟 buffergraphics
。此 image
始终与你的 panel
具有相同的尺寸,因此如果你的 panel
调整大小,则 image
也必须调整大小,并且必须将先前的图像从内存中清除。
// class variables
private int bufferWidth;
private int bufferHeight;
private Image bufferImage;
private Graphics bufferGraphics;
public void paint(Graphics g){
// 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();
}
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();
}
因此,你的 bufferimage
已被创建,现在可以进行 picasso 了。我们向 paint
方法添加一些功能,并创建一个 paintbuffer
方法。之后,屏幕外图像必须复制到屏幕上。一切都一次性绘制在屏幕上,因此再见闪烁。
public void paint(Graphics g){
... resetBuffer();
if(bufferGraphics!=null){
//this clears the offscreen image, not the onscreen one
bufferGraphics.clearRect(0,0,bufferWidth,bufferHeight);
//calls the paintbuffer method with
//the offscreen graphics as a param
paintBuffer(bufferGraphics);
//we finaly paint the offscreen image onto the onscreen image
g.drawImage(bufferImage,0,0,this);
}
}
public void paintBuffer(Graphics g){
//in classes extended from this one, add something to paint here!
//always remember, g is the offscreen graphics
}
现在,你的双缓冲类已准备就绪!
扩展新类
现在,你必须做的就是扩展 DoubleBuffer
并弃用 paintBuffer
方法,而不是 paint
方法。在你的应用程序中,你可以随时调用 repaint
方法,并且屏幕不会闪烁。
public class MyBuffer extends DoubleBuffer{ private int posX; public MyBuffer(){ super(); posX=0; } public void animateToTheRight(){ // this can be called from everywhere, anytime posX++; repaint(); } // attention: we don't touch update() and paint() anymore // we use paintbuffer() instead public void paintBuffer(Graphics g){ /// g is the offscreen graphics g.drawString("Double Buffer!",posX,20); } }
在示例中,如果你添加一个线程并让它调用 animateToTheRight()
,则字符串将从左向右滚动,而不会引起任何闪烁。
瞧,双缓冲正在工作。