65.9K
CodeProject 正在变化。 阅读更多。
Home

标准 Java AWT 中的双缓冲

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.66/5 (19投票s)

2002年4月17日

3分钟阅读

viewsIcon

138957

downloadIcon

440

使用标准 Java AWT 创建双缓冲基类。

引言

你是否曾经创建一个应用程序,在你的窗口中显示动画?你可能已经做过,如果你还没有,你可能有一天会。编码非常简单:你获取一个 Panel,并向其添加一个线程,该线程每 x 毫秒调用 repaint 方法。完成了吗?

不,还没有完全结束。因为,如果你就这样放着它,屏幕会闪烁,你的动画最终会被部分绘制,或者根本没有绘制。

解决这个问题的方法是臭名昭著的双缓冲。

AWT 与 SWING

在 Swing 框架中,你可以通过在你的类中将此属性设置为 true 来添加双缓冲。Swing 正在为你执行所有需要的任务。它们在幕后,你只需要知道它已经完成。闪烁消失了。谢谢 Sun 先生。

但是如果你使用标准的 AWT 呢?Swing 框架相当笨重,并且仍然有一些令人讨厌的错误,因此,在较旧的机器上,你更有可能使用标准的 AWT。但是,这里没有双缓冲,不是吗?

我已经研究这个问题一段时间了,这是我想出的解决方案。这是一个解决方案,而不是唯一的解决方案。我也看过其他的,但它们或多或少都具有相同的组件。

DoubleBuffer 类

由于我们是优秀的 Java 程序员,我们为此创建一个基类,以便我们可以随时实现双缓冲功能。你可以在类 ComponentContainerCanvasPanel 之间进行选择来扩展,但我建议你选择 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(),则字符串将从左向右滚动,而不会引起任何闪烁。

瞧,双缓冲正在工作。

© . All rights reserved.