Introduction
Have you ever created an application that showed an animation in your window? You probably have, and if you have not, you probably will someday. The coding is really simple: you take a Panel
and you add a thread to it that calls the repaint
method every x milliseconds. Done?
No, it's not quiet over yet. Because, if you leave it like that, the screen will flash and flicker, and your animation will end up partially painted, or not painted at all.
The solution for this is the infamous double buffer.
AWT versus SWING
In the Swing framework, you can add the double buffer just by setting this property to true in your class. Swing is carrying out all of the tasks needed for you. They're behind the scenes, and all that you have to know is that it's done. The flicker is gone. Thank you mister Sun.
But what if you use the standard AWT? The Swing framework is rather heavy and still has some nasty bugs, so, on older machines, it's more likely that you use the standard AWT. But, there's no double buffer here, isn't it?
I've studied this problem for some while now, and this is the solution I've come up with. It is a solution, not the solution. I've seen others too, but they all have more or less the same components.
The DoubleBuffer Class
Since we are good Java programmers, we create a base-class for this, so we can implement the double buffer functionality whenever we feel like. You can choose between the classes Component
, Container
, Canvas
and Panel
to extend, but I suggest you choose the Panel
class. If you choose another, you can get problems later on, with some event listeners that will not fire properly.
import java.awt.*;
public class DoubleBuffer extends Panel{
public DoubleBuffer(){
super();
}
Eliminating the Flicker
What's causing the flicker? Well, if you call the repaint
method, you actually tell the VM to repaint this component as soon as possible. You can't tell when (but you can set an initial delay). When the VM has time to carry out the paint tasks, it calls the update
method for you. And there's the first problem already. The update
method clears the panel
for you, so you can paint to it, without concerning about the background. That's what's causing the flicker. It clears the panel
, shows in the window, paints the panel
and shows again. So every paintjob has in fact two jobs, clearing and painting.
We're going to deprecate this.
public void update(Graphics g){
paint(g);
public void paint(Graphics g){
}
Now, the background isn't cleared anymore, so the flicker is gone. If you use this to animate through some images with the same dimensions, you are finished here already, because you don't have to clear the previous image.
But what if you have to clear the previous paintjob?
The Buffer Image
The idea behind double buffering is that you first paint everything to an off-screen image, and, when it's ready, paint it to the screen in just one paintjob. For this purpose, we need a bufferimage
and it's brother buffergraphics
. This image
has always the same dimensions as your panel
, so if your panel
resizes, the image
has to resize also, and the previous has to be cleared out of memory.
private int bufferWidth;
private int bufferHeight;
private Image bufferImage;
private Graphics bufferGraphics;
public void paint(Graphics g){
if(bufferWidth!=getSize().width ||
bufferHeight!=getSize().height ||
bufferImage==null || bufferGraphics==null)
resetBuffer();
}
private void resetBuffer(){
bufferWidth=getSize().width;
bufferHeight=getSize().height;
if(bufferGraphics!=null){
bufferGraphics.dispose();
bufferGraphics=null;
}
if(bufferImage!=null){
bufferImage.flush();
bufferImage=null;
}
System.gc();
bufferImage=createImage(bufferWidth,bufferHeight);
bufferGraphics=bufferImage.getGraphics();
}
So, your bufferimage
has been created and now it's ready to picasso. We add some functionality to the paint
method and create a paintbuffer
method. After that the off-screen image has to be copied to the screen. Everything is painted on the screen in one go, so bye bye flicker.
public void paint(Graphics g){
... resetBuffer();
if(bufferGraphics!=null){
bufferGraphics.clearRect(0,0,bufferWidth,bufferHeight);
paintBuffer(bufferGraphics);
g.drawImage(bufferImage,0,0,this);
}
}
public void paintBuffer(Graphics g){
}
Now, your double buffer class is ready!
Extending the new class
Now, everything you have to do is to extend DoubleBuffer
and deprecate the paintBuffer
method instead of the paint
method. In your application, you can call the repaint
method whenever you feel like, and the screen is not going to flicker once.
public class MyBuffer extends DoubleBuffer{
private int posX;
public MyBuffer(){
super();
posX=0;
}
public void animateToTheRight(){
posX++;
repaint();
}
public void paintBuffer(Graphics g){
g.drawString("Double Buffer!",posX,20);
}
}
In the example, if you add a thread and let it call animateToTheRight()
, a string is scrolling from left to right, without causing any flicker.
Voila, the double buffer is working.