消除闪屏的双缓冲技术在AWT和Swing中的实现

    一、闪屏的原因:
    在用java绘图或者添加图片的时候会出现闪屏的现象,而绘图和添加图片都是要在原来的画布上重画得到的,因此闪屏也跟重画有一定的关系。正是这种先用背景色覆盖组件再重绘图像的方式导致了闪烁。即使时间很短,如果重绘的面积较大的话花去的时间也是比较可观的,这个时间甚至可以大到足以让闪烁严重到让人无法忍受的地步。另外,用paint(Graphics g)函数在屏幕上直接绘图的时候,由于执行的语句比较多,程序不断地改变窗体中正在被绘制的图象,会造成绘制的缓慢,这也从一定程度上加剧了闪烁。
二、什么是双缓冲:
双缓冲技术的工作原理:先在内存中分配一个和我们动画窗口一样大的空间(在内存中的空间我们是看不到的),然后利用getGraphics()方法去获得双缓冲画笔,接着利用双缓冲画笔给空间我们想画的东西,最后将它全部一次性的显示到我门的屏幕上.这样在我门的动画窗口上面是显示出来就非常的流畅了.避免了上面的闪烁效果。

  它的执行过程是这样的:repaint() 到update()再到paint(),而我们的双缓冲代码就写在update()里。

   1)定义一个Graphics对象gOff和一个Image对象offScreenImage。按屏幕大小建立一个缓冲对象给offScreenImage。然后取得offScreenImage的Graphics赋给gOff。此处可以把gOff理解为逻辑上的缓冲屏幕,而把offScreenImage理解为缓冲屏幕上的图象。 

  2) 在gOff(逻辑上的屏幕)上用paint(Graphics g)函数绘制图象。

   3)将后台图象offScreenImage全部一次性的绘制到我们的动画窗口,然后把我们内存中分配的空间窗口关闭调用dispose()方法.

 具体代码如下:

  Image offScreenImage = null;

  if (offScreenImage == null)
        offScreenImage = createImage(BLOCK_SIZE * COLS, BLOCK_SIZE * ROWS);
        Graphics gOff = offScreenImage.getGraphics();
        // 4.调用paint(),将缓冲图象的画笔传入
        paint(gOff);
        // 5.再将此缓冲图像一次性绘到代表屏幕的Graphics对象,即该方法传入的“g”上
        g.drawImage(offScreenImage, 0, 0, null);


下面我们具体看在AWT和Swing中双缓冲的使用:
在awt中对于窗体画布的重绘其条用顺序是repaint() —>update()—>paint();java中update()的源代码为:
 
/**     
     * Updates the container.  This forwards the update to any lightweight    
     * components that are children of this container.  If this method is    
     * reimplemented, super.update(g) should be called so that lightweight    
     * components are properly rendered.  If a child component is entirely    
     * clipped by the current clipping setting in g, update() will not be    
     * forwarded to that child.    
     *    
     * @param g the specified Graphics window    
     * @see   Component#update(Graphics)    
     */    
    public void update(Graphics g) {     
        if (isShowing()) {     
            if (! (peer instanceof LightweightPeer)) {     
                g.clearRect(0, 0, width, height);     
            }     
            paint(g);     
        }     
}    

     可以看到update中有一个清屏的动作,即 g.clearRect(0, 0, width, height);然后调用   paint(g); 函数进行重绘。
  屏幕上之所以出现闪烁是因为在update()方法内先要清空屏幕上原有的东西,然后再往上画,所以在我们需要不断重绘的屏幕上出现闪烁是必然的了。
    因此在awt中解决闪烁问题的思路为重写update()函数的代码,改变它的工作原理。通过重写update()方法改变重绘函数paint(g)重绘的画布对象g。
例如重写update()方法如下:

// 重写update方法,先将窗体上的图形画在图片对象上,再一次性显示 
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.BLACK);
// 有清除上一步图像的功能,相当于gImage.clearRect(0, 0, WIDTH, HEIGHT)   
gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
gOffScreen.setColor(c);
// 将截下的图片上的画布传给重绘函数,重绘函数只需要在截图的画布上绘制即可,不必在从底层绘制
paint(gOffScreen);
//将截下来的图片加载到窗体画布上去,才能达到每次重画的效果
g.drawImage(offScreenImage, 0, 0, null);
}

但是上述方法在Swing中就失效了,为什么呢?
在swing中update()方法并没有像awt的update()那样随时被调用,所以就很好解释为什么该为继承JFrame之后屏幕重绘闪烁了。即便是你重写了update()方法,但是系统根本没有调用所以重写了也白搭。
所以要解决这个问题我们就要考虑到双缓冲的核心就是改变paint(g)中的画布,那么直接在paint(g)函数里实现就可以了。
public void paint(Graphics g) {     
        // 在重绘函数中实现双缓冲机制     
        offScreenImage = this.createImage(WIDTH, HEIGHT);     
        // 获得截取图片的画布     
        gImage = offScreenImage.getGraphics();     
        // 获取画布的底色并且使用这种颜色填充画布,如果没有填充效果的画,则会出现拖动的效果     
        gImage.setColor(gImage.getColor());     
        gImage.fillRect(0, 0, WIDTH, HEIGHT); // 有清楚上一步图像的功能,相当于gImage.clearRect(0, 0, WIDTH, HEIGHT)     
    
        // 调用父类的重绘方法,传入的是截取图片上的画布,防止再从最底层来重绘     
        super.paint(gImage);     
    
         //将截下来的图片加载到窗体画布上去,才能达到每次重画的效果
        g.drawImage(offScreenImage, 0, 0, null);     
    }   

最重要的是super.paint(gImage)这句,改变画布在这里,消除闪烁也是在这里。
那么Swing中双缓冲是怎样的呢?
从继承体系来看,JFrame->Frame->Window->Container->Component,在Frame中的update()方法是从Container中继承而来的,而JFrame中却重写了update()方法如下
/**     
 * Just calls paint(g).  This method was overridden to     
 * prevent an unnecessary call to clear the background.    
 *    
 * @param g the Graphics context in which to paint    
 */    
public void update(Graphics g) {     
    paint(g);     
}   
这里直接调用了paint()函数而没有clearRect(),也就是清屏的方法,这里他试图不通过清屏来阻止闪烁的发生。这也就是JFrame本身的一种处理方法。

在Java游戏编程时,使用Swing组件绘图时,一般需要覆盖组件的paintComponent(Graphics)方法,在该方法中写绘图代码,需要注意的是,在绘图代码之前要调用super.paintComponent(g)来清除一个遮光部件的背景。(某些人为图简单省略掉,不要不写)。

  此外,如果需要重画,则使用repaint方法,repaint方法会自动调用paintComponent(Graphics)方法。

  Java游戏编程中,绘制圆的代码写在Jpanel类的paintComponent(Graphics)方法中,然后在多线程中不断调用repaint方法,从而不断调用paintComponent(Graphics)方法绘出移动的圆形。


  如果是在AWT组件内绘图,比如Frame,则需要把绘图代码写在paint(Graphics)方法中。如果需要不断重画,则需要在多线程中调用repaint方法,repaint会自动调用update方法,update方法会自动调用paint方法完成重画。


  Java游戏编程中还有一点需要注意:在Swing中绘图,Java提供了双缓冲的功能,而AWT中绘图则没有双缓冲的功能,从而绘图会出现闪烁的问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值