对于双缓冲的分析是在坦克大战游戏的设计时开始的,由于当时忙于游戏的整体设计,所以对这一个问题没有进行详细的研究,现在就这个问题来谈谈自己的一些看法。
分析前提出几个问题:
1、为什么当想屏幕上添加图片之后会有明显的闪烁现象?
2、在awt中如何实现双缓冲?
3、如何理解swing内置双缓冲以及比较他与awt中消除闪烁的方法区别在哪里?
首先我们来解答第一个问题:
我们在屏幕上自绘图形或者是添加图片都是要通过所在画布的重绘来实现的,因此闪烁的出现必然与重绘机制有着一些关联。在awt中对于窗体画布的重绘其条用顺序是repaint() —>update()—>paint();我们来看看update()的源码:
Java代码
- /**
- * 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()方法内先要哗哗的清空屏幕上原有的东西,然后又哗哗的往上画,所以在我们需要不断重绘的屏幕上出现闪烁是必然的了,哪怕CPU的速度快之又快。
通过上述的分析,在awt中我们解决闪烁问题的思路也因该随之产生,即重写update()函数的代码,改变它的工作原理。于是我们引进一段在坦克大战中已经重写了的update()方法。其中通过改变重绘函数paint(g)重绘的画布对象,由窗体的画布变为截取的图片上的画布gImage,这样的话就很大程度上改善这个问题了。具体如下
Java代码
- // 重写update方法,先将窗体上的图形画在图片对象上,再一次性显示
- public void update(Graphics g) {
- if (offScreenImage == null) {
- // 截取窗体所在位置的图片
- offScreenImage = this.createImage(WIDTH, HEIGHT);
- }
- // 获得截取图片的画布
- Graphics gImage = offScreenImage.getGraphics();
- // 获取画布的底色并且使用这种颜色填充画布(默认的颜色为黑色)
- Color c = Color.BLACK;
- gImage.setColor(c);
- gImage.fillRect(0, 0, WIDTH, HEIGHT); // 有清除上一步图像的功能,相当于gImage.clearRect(0, 0, WIDTH, HEIGHT)
- // 将截下的图片上的画布传给重绘函数,重绘函数只需要在截图的画布上绘制即可,不必在从底层绘制
- paint(gImage);
- //将接下来的图片加载到窗体画布上去,才能考到每次画的效果
- g.drawImage(offScreenImage, 0, 0, null);
- }
其实一言以蔽之就是通过重写update()方法改变重绘函数paint(g)重绘的画布对象g。
以上的讨论我们都是在awt中进行,然后大家就想将继承Frame改为JFrame试试,结果一试就傻眼了,屏幕上居然又是哗哗的闪了,真是辛辛苦苦去改变,一下回到解放前,我们不是在update()中实现双缓冲机制了吗?请看下面的一个对比测试:
(1)在awt中测试update():
Java代码
- // 重写update方法,先将窗体上的图形画在图片对象上,再一次性显示
- public void update(Graphics g) {
- System.out.println("awt的update()在此...");
- if (offScreenImage == null) {
- // 截取窗体所在位置的图片
看看结果:
要是没觉得意外的话就继续往下看
在swing中测试update():
Java代码
- // 重写update方法,先将窗体上的图形画在图片对象上,再一次性显示
- public void update(Graphics g) {
- System.out.println("Swing的update()在此...");
- if (offScreenImage == null) {
- // 截取窗体所在位置的图片
结果是:
是不是有点吃惊了,在我没有故意编出这个东西忽悠大伙的前提下我们可以得知,在swing中update()方法并没有像awt的update()那样随时被调用,所以就很好解释为什么该为继承JFrame之后屏幕重绘闪烁了。就是你认为自己改写了update()方法就会解决这个问题是一厢情愿的,系统并不买你的帐,调都没去调用呐!
那么怎么通过其他的方法消除swing中的闪烁问题呢,我们此时再回到出发点,双缓冲的核心就是改变paint(g)中的画布,那么好了,我直接在paint(g)函数里实现不就得了,下面再来看这一段代码:
Java代码
- 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);
- // 当游戏没有结束的时候绘出对战双方
- if (!getGameOver()) {
- // 画出自己的坦克
- paintMyTank(gImage);
- // 画出自己坦克发射的子弹
- paintMyBullet(gImage);
- // 画出敌方坦克
- paintEnemyTank(gImage);
- // 画出敌方坦克发射的子弹
- paintEnemyBullet(gImage);
- }
- // 画出草地
- paintGrass(gImage);
- // 画出小河
- paintRiver(gImage);
- // 画出石头
- paintStone(gImage);
- // 画出各种道具
- paintTool(gImage);
- // 将接下来的图片加载到窗体画布上去,才能考到每次画的效果
- g.drawImage(offScreenImage, 0, 0, null);
- }
有一些相似的部分吧,其中最重要的是super.paint(gImage)这句,改变画布在这里,消除闪烁也是在这里!!!
下面我们再探讨最后一个问题,即如何理解swing中内置双缓冲,我们首先从继承体系来看,JFrame->Frame->Window->Container->Component,在Frame中的update()方法是从Container中继承而来的,而JFrame中却重写了update()方法如下
Java代码
- /**
- * 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本身的一种处理方法。
以上是通过自己对双缓冲的一些理解,其中还有很多问题,希望牛人们能够积极指出来,并且一起讨论这个问题。
【编辑推荐】