最近在做Java版贪吃蛇的入门项目,过程中遇到窗口闪烁的问题总结。
一、AWT组件开发
1、AWT
AWT是抽象窗口工具箱的缩写,它为编写图形用户界面提供了用户接口,通过这个接口就可以继承很多方法,省去了很多工作。AWT还能使应用程序更好地同用户进行交互。
AWT中的容器是一种特殊的组件,他可以包含其他组件,即可以把组件方法容器中。Container类是用来存放其他组件的Component类的子类,Frame类又是Component的子类。Frame类用于创建具有标题栏和边界的窗口。这里通过继承Frame类来建立自己的界面。
二、Swing界面编程
随着Java的发展,AWT已经渐渐被淘汰,它已经不能适应发展的需要,不能满足开发功能强大的用户界面的需要。这时Swing出现了,它是建立在AWT之上的组件集,在不同的平台上都能保持组件的界面样式,因此得到了非常广泛的应用。
1、Swing组件库
在Swing组件中有许多种组件,它们被封装在JFC中,下面我们会对每一种组件进行详细介绍。Swing包很多,但平常用到的只有javax.swing.*和javax.swing.event.*这两个包,其他的很少用到。
1)、JFC结构
JFC是Java的基础类,是Java Foundation Classes的缩写形式,封装了一组用于构建图形用户界面的组件和特性。JFC包含了图形用户界面构建中需要用到的顶级容器(Applet、Dialog、Frame)、普通容器(面板、滚动面板、拆分窗格组件、选项卡插U能给个和工具条等)、特殊容器(InternalFrame、Layeredpane、root pane)、基本组件(button , combo box , list , menu , slider , spinner和textfild)等。
2)、与AWT的区别
最大的区别在于Swing组件的实现与本地实现无关。Swing组件比AWT组件具有更多的功能。例如在Swing中添加了按钮组件和标签组件,通过继承来更改Swing组件的行为和外观,访问技术等。
在游戏中相应的实现即主要窗体用Frame和JFrame来构建。
1.Frame:重量级组件
2.JFrame:轻量级组件
出现问题:
①.窗体调用repaint()方法时闪烁严重
②.窗体设置双缓冲重绘后,DrawImage()进行缩放图片时会失真,Graphics2D设置抗锯齿属性也会失效
Ps:问题到目前为止还没有很好的处理,可能相应的用JPanel进行绘制会解决问题(暂未尝试!)
在Awt中对于窗体画布的重绘其条用顺序是repaint() —>update()—>paint();
默认的upadate()中自带clearRect()方法,即清屏功能,程序运行时我们调用repaint()方法刷新则会造成屏幕刚清空,又继续重新调用paint()方法往窗体上绘制,则就造成闪烁问题!
public void update(Graphics g) {
if (isShowing()) {
if (! (peer instanceof LightweightPeer)) {
g.clearRect(0, 0, width, height);
}
paint(g);
}
}
所以闪烁问题我们需要重写update()方法来实现双缓冲
代码:(以本人贪吃蛇项目为例)
public void update(Graphics g) {
// 截取窗体所在位置的图片
if (image == null)
image = this.createImage(this.getWidth(), this.getHeight());
// 获得截取图片的画布
Graphics gre = image.getGraphics();
// 获取画布的底色并且使用这种颜色填充画布(默认的颜色为黑色)
gre.setColor(gre.getColor());
// 有清除上一步图像的功能,相当于gImage.clearRect(0, 0, WIDTH, HEIGHT)
gre.fillRect(0, 0, this.getWidth(), this.getHeight());
// 将截下的图片上的画布传给重绘函数,重绘函数只需要在截图的画布上绘制即可,不必在从底层绘制
paint(gre);
//将接下来的图片加载到窗体画布上去,才能考到每次画的效果
g.drawImage(image, 0, 0, null);
}
而Swing中内置双缓冲,我们首先从继承体系来看,JFrame->Frame->Window->Container->Component,在Frame中的update()方法是从Container中继承而来的,而JFrame中却重写了update()方法如下:
public void update(Graphics g) {
paint(g);
}
对比之后会发现直接调用了paint()函数而没有clearRect(),试图不通过清屏来阻止闪烁的发生。这也就是JFrame本身的一种处理方法。
但是我们用JFrame编程并且重新update()实现双缓冲后,窗体一样狂闪不停!原因为何?
下面引用一张图来说明:
repaint()方法在重量级组件的时候会调用update方法,在轻量级组件的时候会调用paint方法
即JFrame根本不会去调用update()方法!
解决方法:在Paint()中直接进行双缓冲操作
代码:
if (image == null)
// 截取窗体所在位置的图片
if (image == null)
image = this.createImage(this.getWidth(), this.getHeight());
// 获得截取图片的画布
Graphics gre = image.getGraphics();
// 获取画布的底色并且使用这种颜色填充画布(默认的颜色为黑色)
gre.setColor(gre.getColor());
// 有清除上一步图像的功能,相当于gImage.clearRect(0, 0, WIDTH, HEIGHT)
gre.fillRect(0, 0, this.getWidth(), this.getHeight());
//调用父类的重绘方法,传入的是截取图片上的画布,防止再从最底层来重绘
super.paint(imageG );
//蛇身
if (snake != null) {
snake.drawSnake(imageG );
snake.scoreView(imageG );
}
//墙体
if (wall != null)
wall.drawWall(imageG );
//食物(金币)
if (food != null)
food.drawFood(imageG );
//将接下来的图片加载到窗体画布上去,才能考到每次画的效果
g.drawImage(image, 0, 0, null);
}
其中最重要的是 super.paint(imageG );
这里必须先调用父类Frame的方法刷新屏幕清理上一次repaint画出的图像。
然后可以创建一个线程让程序每隔一段时间后自动调用repaint()方法;