对于双缓沖的分析是在坦克大战游戏的设计时开始的由于当时忙于游戏的整体设计所以对这一个问题没有进行详细的研究现在就这个问题来谈谈自己的一些看法 分析前提出几个问题 为什么当想屏幕上添加图片之后会有明显的闪烁现象? 在awt中如何实现双缓沖? 如何理解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 superupdate(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)) { gclearRect( width height); } paint(g); } } 从这里我们可以清晰的看到update中有一个清屏的作用即gclearRect( width height);然后再在下面调用paint(g)函数进行重绘因此到这里的话我们可以在一定程度上对底层的重绘机制有一个了解了 现在我们明白了屏幕上之所以出现闪烁是因为在update()方法内先要哗哗的清空屏幕上原有的东西然后又哗哗的往上画所以在我们需要不断重绘的屏幕上出现闪烁是必然的了哪怕CPU的速度快之又快 通过上述的分析在awt中我们解决闪烁问题的思路也因该随之产生即重写update()函数的代码改变它的工作原理于是我们引进一段在坦克大战中已经重写了的update()方法其中通过改变重绘函数paint(g)重绘的画布对象由窗体的画布变为截取的图片上的画布gImage这样的话就很大程度上改善这个问题了具体如下 Java代码 // 重写update方法先将窗体上的图形画在图片对象上再一次性显示 public void update(Graphics g) { if (offScreenImage == null) { // 截取窗体所在位置的图片 offScreenImage = thiscreateImage(WIDTH HEIGHT); } // 获得截取图片的画布 Graphics gImage = offScreenImagegetGraphics(); // 获取画布的底色并且使用这种颜色填充画布(默认的颜色为黑色) Color c = ColorBLACK; gImagesetColor(c); gImagefillRect( WIDTH HEIGHT); // 有清除上一步图像的功能相当于gImageclearRect( WIDTH HEIGHT) // 将截下的图片上的画布传给重绘函数重绘函数只需要在截图的画布上绘制即可不必在从底层绘制 paint(gImage); //将接下来的图片加载到窗体画布上去才能考到每次画的效果 gdrawImage(offScreenImage null); } 其实一言以蔽之就是通过重写update()方法改变重绘函数paint(g)重绘的画布对象g 以上的讨论我们都是在awt中进行然后大家就想将继承Frame改为JFrame试试结果一试就傻眼了屏幕上居然又是哗哗的闪了真是辛辛苦苦去改变一下回到解放前我们不是在update()中实现双缓沖机制了吗?请看下面的一个对比测试 ()在awt中测试update(): Java代码 // 重写update方法先将窗体上的图形画在图片对象上再一次性显示 public void update(Graphics g) { Systemoutprintln(awt的update()在此); if (offScreenImage == null) { // 截取窗体所在位置的图片 看看结果 要是没觉得意外的话就继续往下看 在swing中测试update(): Java代码 // 重写update方法先将窗体上的图形画在图片对象上再一次性显示 public void update(Graphics g) { Systemoutprintln(Swing的update()在此); if (offScreenImage == null) { // 截取窗体所在位置的图片 结果是 是不是有点吃惊了在我没有故意编出这个东西忽悠大伙的前提下我们可以得知在swing中update()方法并没有像awt的update()那样随时被调用所以就很好解释为什么该为继承JFrame之后屏幕重绘闪烁了就是你认为自己改写了update()方法就会解决这个问题是一厢情愿的系统并不买你的帐调都没去调用吶! 那么怎么通过其他的方法消除swing中的闪烁问题呢我们此时再回到出发点双缓沖的核心就是改变paint(g)中的画布那么好了我直接在paint(g)函数里实现不就得了下面再来看这一段代码 Java代码 public void paint(Graphics g) { // 在重绘函数中实现双缓沖机制 offScreenImage = thiscreateImage(WIDTH HEIGHT); // 获得截取图片的画布 gImage = offScreenImagegetGraphics(); // 获取画布的底色并且使用这种颜色填充画布如果没有填充效果的画则会出现拖动的效果 gImagesetColor(gImagegetColor()); gImagefillRect( WIDTH HEIGHT); // 有清楚上一步图像的功能相当于gImageclearRect( WIDTH HEIGHT) // 调用父类的重绘方法传入的是截取图片上的画布防止再从最底层来重绘 superpaint(gImage); // 当游戏没有结束的时候绘出对战双方 if (!getGameOver()) { // 画出自己的坦克 paintMyTank(gImage); // 画出自己坦克发射的子弹 paintMyBullet(gImage); // 画出敌方坦克 paintEnemyTank(gImage); // 画出敌方坦克发射的子弹 paintEnemyBullet(gImage); } // 画出草地 paintGrass(gImage); // 画出小河 paintRiver(gImage); // 画出石头 paintStone(gImage); // 画出各种道具 paintTool(gImage); // 将接下来的图片加载到窗体画布上去才能考到每次画的效果 gdrawImage(offScreenImage null); } 有一些相似的部分吧其中最重要的是superpaint(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本身的一种处理方法 以上是通过自己对双缓沖的一些理解其中还有很多问题希望牛人们能够积极指出来并且一起讨论这个问题 |