双缓冲的使用—java语言

时间:2021-08-21 12:34:28

这里先给出一段代码,功能是用键盘控制屏幕上的一个小矩形移动

import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class MainWindow extends Frame implements KeyListener{
final int WINDOW_WIDTH=800;
final int WINDOW_HEIGHT=700;
final int WINDOW_X=100;
final int WINDOW_Y=100;
final String title="坦克大战";
final Color backgroundColor=Color.green;
public int tank_x=100;
public int tank_y=100;
public MainWindow(){
this.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
this.addKeyListener(this);
this.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
this.setLocation(WINDOW_X, WINDOW_Y);
this.setTitle(title);
this.setBackground(backgroundColor);
}
public void paint(Graphics g){
g.setColor(Color.red);
g.fillRect(tank_x, tank_y, 30, 30);
}
public static void main(String[] args) {
MainWindow mainWindow=new MainWindow();
mainWindow.setVisible(true);
}
public void keyPressed(KeyEvent arg0) {
if(arg0.getKeyCode()==KeyEvent.VK_UP)
tank_y-=1;
if(arg0.getKeyCode()==KeyEvent.VK_DOWN)
tank_y+=1;
if(arg0.getKeyCode()==KeyEvent.VK_LEFT)
tank_x-=1;
if(arg0.getKeyCode()==KeyEvent.VK_RIGHT)
tank_x+=1;
repaint();
}
public void keyReleased(KeyEvent arg0) {
}
public void keyTyped(KeyEvent arg0) {
}
}

运行结果如下图所示:
双缓冲的使用—java语言
这段代码成功实现了用键盘控制矩形移动的功能,但是有个严重的问题就是矩形在运动的过程中有严重的闪烁,这一点让人头疼,要解决问题,咱们得先找到问题,先来分析一下代码吧:
MainWindow的对象建立后,程序会首先调用重写后的paint()方法,在窗口上绘制一个矩形,当我们按下键盘上的键时,矩形的坐标值已经改变了,我们调用repaint()这个方法,这个方法会重新调用paint()方法,然后在新的位置画一个矩形,这里有一个问题就是在新的位置画了矩形之后,原来的矩形还在,这样矩形的运动就会留下“轨迹”,为了解决这个问题,repaint()方法在调用paint()方法的时候会先调用一个update()方法来把原来的屏幕清掉,然后update()方法再调用paint()方法在新的位置画一个矩形,这样就把矩形的运动轨迹给清掉了,达到了运动的效果。函数调用示意图如下:
双缓冲的使用—java语言
问题就发生在update()这个函数中,我们来看一下这个函数的源码:

public void update(Graphics g) {
if (isShowing()) {
if (! (peer instanceof LightweightPeer)) {
g.clearRect(0, 0, width, height);
}
paint(g);
}
}

代码的意思是:(如果该组件是轻量组件的话)先用背景色覆盖整个组件,然后再调用paint(Graphics g)函数,重新绘制矩形)我们每次看到的都是一个在新的位置绘制的矩形,前面的矩形都被背景色覆盖掉了。这就像一帧一帧的画面匀速地切换,以此来实现动画的效果。但是,正是这种先用背景色覆盖组件再重绘图像的方式导致了闪烁。在两次看到不同位置矩形的中间时刻,总是存在一个在短时间内被绘制出来的空白画面(颜色取背景色)。但即使时间很短,如果重绘的面积较大的话花去的时间也是比较可观的,这个时间甚至可以大到足以让闪烁严重到让人无法忍受的地步。1. 既然已经知道了问题产生的原因,那就来解决问题吧。我们在update这个函数里做做文章吧,首先咱们重写一下这个函数:

public void update(Graphics g){
paint(g);
}

在这个函数里我们只调用paint函数而不做其他处理,运行结果如下:
双缓冲的使用—java语言
这样写矩形的轨迹又出来了,因为我们没有在update函数里进行清屏的操作,下面我们在update函数里写其他代码,由于我们已经知道了闪烁的原因是因为清屏的时候用背景色覆盖了原来的画面,所以这里我们要让这个过程改变一下。既然可以用背景色覆盖原来的画面,我们可以试一下在一张图片上画好新的矩形,然后用这张图片覆盖原来的画面,代码如下:

public void update(Graphics g){
if(i==null)
i=this.createImage(WINDOW_WIDTH, WINDOW_HEIGHT);
Graphics ig=i.getGraphics();
ig.setColor(backgroundColor);
ig.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
paint(ig);
g.drawImage(i, 0, 0, null);
}

首先是让这个窗口生成一张图片:

i=this.createImage(WINDOW_WIDTH, WINDOW_HEIGHT);

然后给这张图片涂上背景色:

Graphics ig=i.getGraphics();
ig.setColor(backgroundColor);
ig.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

在这里重新定义了一只画笔ig,是这张纸自带的画笔,然后调用paint函数在纸上画个矩形:

paint(ig);

最后把这张图片给画出来:

g.drawImage(i, 0, 0, null);

运行程序发现运动的矩形不再闪烁了,这样我们就完成了简单的双缓冲技术。
除了这种方法外,还可以在paint函数里做文章,原理也是一样,代码如下:

public void paint(Graphics g){
if(i==null)
i=this.createImage(WINDOW_WIDTH, WINDOW_HEIGHT); //生成一张纸
Graphics ig=i.getGraphics(); //定义一只新画笔
ig.setColor(backgroundColor); //给画笔选颜色
ig.fillRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); //给这张纸涂上背景色

ig.setColor(Color.red); //设置画笔颜色
ig.fillRect(tank_x, tank_y, 30, 30); //在屏幕上画一个圆
g.drawImage(i, 0, 0, null);
}
//下面这个函数是实现双缓冲的
public void update(Graphics g){
paint(g); //调用paint函数在纸上画矩形
}

我们在update函数里只调用paint函数,而在paint函数里实现了双缓冲,两种方法原理一样,要灵活掌握,这里给出双缓冲的一般步骤:
(1)定义一个Graphics对象ig和一个Image对象i。按屏幕大小建立一个缓冲对象给i。然后取得i的Graphics赋给ig。此处可以把ig理解为逻辑上的缓冲屏幕,而把i理解为缓冲屏幕上的图象。
(2)在ig(逻辑上的屏幕)上用paint(Graphics g)函数绘制图象。
(3)将后台图象i绘制到前台。

以上就是一次双缓冲的过程。注意,将这个过程联系起来的是repaint()函数。paint(Graphics g)是一个系统调用语句,不能由程序员手工调用。只能通过repaint()函数调用。