快捷搜索:

java指南之使用图形:动画

履行动画

所有形式的动画的一个共通点便是它们经由过程以相对而言较快的速率显示继续的画面创造某种能被觉察的运动效果。谋略灵便画平日每秒显示10-20帧。对照而言,传统的手工绘制的动画每秒为8帧(低质的动画)到12帧(标准动画)以至到24帧(平滑效果,现实中的运动效果)。下面的几页将奉告你在java中履行动画所必要知道的所有常识。

--------------------------------------------------------------------------------

开始前: 反省是否有动画对象和诸如 Animator这样的applet,看看你是否可以用它们来代替自己写法度榜样。

--------------------------------------------------------------------------------

应用Timer创建轮回的动画

创建一个动画法度榜样的最紧张的一步是精确的制订计划。除了动画仅仅直接反映外部事故(例如用户拖动屏幕上的工具)外,法度榜样必要一个动画轮回。

动画轮回有责任跟踪当前帧并且周期性的哀求屏幕更新。对付applet和很多利用法度榜样,你必要一个零丁的线程运行动画轮回。这一节包孕一个典型applet和一个典型法度榜样,它们都应用Timer 工具实现动画轮回。你可以将这些例子作为你自己的动画的模板。

在屏幕上移动图像

最简单的动画形式是在屏幕上移动一个固定的图像。在传统的动画天下中,这被称为剪纸动画(cutout animation),由于它平日是经由过程剪切外形并在镜头前移动达到的。

显示一列图像

这一节奉告你若何履行经典的卡通式的动画:显示一列图像。

增强外不雅和前进动画机能。

这一节奉告你若何应用MediaTracker,它可以使你直到动画的所有的图像都被加载后才显示动画。你也会获得一些经由过程合并图像文件和应用压缩规划的措施前进动画机能的提示。

应用Timer创建轮回的动画

经由过程以规则的距离履行动画的每个法度榜样必要一个动画轮回。平日,这个轮回应该在自己的线程中。它永世不应该在 paintComponent 措施中,由于它将接收事故分派线程,该线程掌管绘图和事故处置惩罚。

Timer类使得实现一个动画轮回很轻易。这一节两个基于Timer的动画模板,一个用于applet,另一个用于利用法度榜样。applet版本的照片鄙人面在运行该applet时,你可以单击它竣事动画,再次单击可以让它继承。

这个图片是该applet的GUI。要运行那个applet,单击图片。该applet将在一个新浏览窗口显示。

这个模板动画的行径有点让人厌烦:它简单的显示当前的帧数,应用缺省的每秒10帧的速率。下面的几节建立这个典型,向你展示若何制作图像动画。

你可以从 AnimatorAppletTimer.java获得applet版本模板的代码。利用法度榜样版本模板的代码在 AnimatorApplicationTimer.java中。这一节的余下的部分化释模板中合营的代码.下面是两个模板所做的工作的概览:

public class AnimatorClass ... implements ActionListener {

int frameNumber = -1;

Timer timer;

boolean frozen = false;

JLabel label;

//初始化:

//从用户指定的帧/秒值抉择帧间的距离光阴。

...

//设置一个准时器调用这个工具的事故处置惩罚器。

timer = new Timer(delay, this);

...

//设置GUI的组件。

public synchronized void startAnimation() {

...

timer.start();

...

}

public synchronized void stopAnimation() {

...

timer.stop();

...

}

public void actionPerformed(ActionEvent e) {

//增添动画帧。

frameNumber++;

//哀求重绘帧

label.setText("Frame " + frameNumber);

}

...

//当法度榜样的GUI显示时:

startAnimation();

...

}

初始化实例变量

模板应用了四个实例变量。第一个 (frameNumber)表示当前帧。它被初始化为-1,纵然是第一帧数是0。缘故原由是帧数在动画轮回的开始就被增添,这个光阴先于第一帧被绘制的光阴。是以,第一次被绘制的帧数是0。

第二个实例变量(timer)是一个Timer工具,它实现动画轮回。它被初始化为每隔delay毫妙履行一次事故。

变量delay是一个局部变量,由用户供给的帧/秒 数初始化。下面的代码将该值转换为帧间的毫秒数:

delay = (fps > 0) ? (1000 / fps) : 100;

前面代码中的? : 符号是 if else 的简短形式。假如用户供给一个大年夜于0的帧/秒值,那么距离便是1000毫秒除以该值。否则距离便是100毫秒(10帧/秒)。

第三个实例变量是 (frozen)是一个boolean 值,它被初始化为false。当用户哀求竣事动画,模板将它设置为 true 。在这一节的后面你会看到有关它的更多信息。

第四个实例变量(label)是履行绘图的组件的一个引用。

动画轮回

Timer 工具经由过程每delay毫秒触发一次事故实现动画轮回。对应于每次事故,actionPerformed 措施履行下面的功能:

增添帧数

哀求绘制当前帧。

要得到更多有关准时器的信息,请参考 若何应用准时器。

礼貌行径

动画模板有两个出于礼貌的特点。

第一个是运行用户明确的竣事(和从新启动)动画,同时applet或者法度榜样保持可见。动画可能会使民心烦意乱,是以给予用户竣事动画而将留意力集中在其它的工作上的能力是个不错的主见。这个特点是经由过程重写 mousePressed 措施实现的,它根据法度榜样确当前状态抉择是竣事照样从新启动准时器。下面是实现的代码:

...//初始化代码中:

boolean frozen = false;

...

public synchronized void startAnimation() {

if (frozen) {

//什么也不做。用户哀求竣事图像变更。

} else {

//开始动画!

...

timer.start();

...

}

public synchronized void stopAnimation() {

...

timer.stop();

...

}

...

//动画组件的鼠标 监听 器中:

public void mousePressed(MouseEvent e) {

if (frozen) {

frozen = false;

startAnimation();

} else {

frozen = true;

stopAnimation();

}

}

第二个特点是在applet或者法度榜样弗成见的时刻挂起动画。对付 applet模板,这是经由过程在stop 和 start 措施平分手调用stopAnimation 和startAnimation措施达到的。对付法度榜样模板,这是经由过程实现窗口事故处置惩罚器达到的,该处置惩罚看重定义最小化和规复,在此中再次分手调用stopAnimation 和startAnimation 。

在两个模板中,假如用户没有竣事动画,那么当法度榜样检测到动画弗成见时,它看护准时器竣事。当用户再次造访动画,法度榜样从新启动动画,除非用户明确哀求竣事动画。

Moving an Image Across the Screen

This page features an example applet that moves one image (a rocketship) in front of a background image (a field of stars). You could implement this in one of two ways -- either using one label per image, or using one custom component that paints both images. Because this lesson features painting, this section features the custom component approach, as implemented in MovingImageTimer.java.

--------------------------------------------------------------------------------

Note: You can also see an alternate implementation, which uses labels and a layered pane. You can find it in MovingLabels.java, which you can run by visiting MovingLabels.html.

--------------------------------------------------------------------------------

Below are the two images this applet uses.

rocketship.gif:

starfield.gif:

Here´s a picture of the applet´s GUI. Remember that you can click on the applet to stop or start the animation.

This is a picture of the applet´s GUI. To run the applet, click the picture. The applet will appear in a new browser window.

--------------------------------------------------------------------------------

Note: The rocketship image has a transparent background. The transparent background makes the rocketship image appear to have a rocketship shape, no matter what color background it´s painted on top of. If the rocketship background weren´t transparent, then instead of the illusion of a rocketship moving through space, you´d see a rocketship on top of a rectangle moving through space.

--------------------------------------------------------------------------------

The code for performing this animation isn´t complex. Essentially, it´s a copy of the animation template that, instead of using a label to perform animation, uses a custom component. The custom component is a JPanel subclass that paints two images, one of which has a position that depends on the current frame number. Here is the code that paints the custom component:

...//Where the images are initialized:

Image background = getImage(getCodeBase(),

"images/rocketship.gif");

Image foreground = getImage(getCodeBase(),

"images/starfield.gif");

...

public void paintComponent(Graphics g) {

super.paintComponent(g); //paint any space not covered

//by the background image

int compWidth = getWidth();

int compHeight = getHeight();

//If we have a valid width and height for the

//background image, paint it.

imageWidth = background.getWidth(this);

imageHeight = background.getHeight(this);

if ((imageWidth > 0) && (imageHeight > 0)) {

g.drawImage(background,

(compWidth - imageWidth)/2,

(compHeight - imageHeight)/2, this);

}

//If we have a valid width and height for the

//foreground image, paint it.

imageWidth = foreground.getWidth(this);

imageHeight = foreground.getHeight(this);

if ((imageWidth > 0) && (imageHeight > 0)) {

g.drawImage(foreground,

((frameNumber*5)

% (imageWidth + compWidth))

- imageWidth,

(compHeight - imageHeight)/2,

this);

}

}

You might think that this program doesn´t need to clear the background, since it uses a background image. However, clearing the background is still necessary. One reason is that the applet usually starts painting before the images are fully loaded. If the rocketship image loaded before the background image, you would see parts of multiple rocketship until the background image loaded. Another reason is that if the applet painting area were wider than the background image, for some reason, then you´d see multiple rocketships to either side of the background image.

You could solve the first problem by delaying all painting until both images are fully loaded. The second problem could be solved by scaling the background image to fit the entire applet area. You´ll learn how to wait for images to be fully loaded in Improving the Appearance and Performance of Image Animation, later in this lesson. Scaling is described in Displaying Images.

显示一列图像

这一节的典型给出显示一列图像的根基,下一节有关于增强外不雅和动画机能的提示。这一节只显示applet代码,对付利用法度榜样的代码很相似,除了应该应用加载图像 中描述的措施加载图像。

下面是这个applet应用的十个图像。

T1.gif: T2.gif: T3.gif: T4.gif: T5.gif:

T6.gif: T7.gif: T8.gif: T9.gif: T10.gif:

下面是applet的图片。记着你可以点击applet让它竣事和从新开始动画。

这个图片是该applet的GUI。要运行那个applet,单击图片。该applet将在一个新浏览窗口显示。

这个典型的代码在 ImageSequenceTimer.java中,它以致比前一个移动图像的典型的代码还简单。下面是和移动图像的典型的代码显着不合的地方:

. . .//在初始化代码中:

Image[] images = new Image[10];

for (int i = 1; i <= 10; i++) {

images[i-1] = getImage(getCodeBase(), "images/duke/T"+i+".gif");

}

. . .//在paintComponent措施中:

g.drawImage(images[ImageSequenceTimer.frameNumber % 10],

0, 0, this);

实现这个典型的另一个措施是应用一标签显示图像,不应用自定义绘图代码,应用setIcon 措施改变要显示的图像。

增强外不雅和动画机能

你可能留意到了上一页的动画的两个问题:

在图像还在加载时,法度榜样部分的显示某些图像,有些根本就不显示。

加载图像花费的光阴太长。

显示部分图像的问题很轻易修正,应用 MediaTracker 类。 MediaTracker 也能削减图像加载的光阴。处置惩罚图像加载迟钝的另一个措施因此某种要领改变图像款式;这一页对此给出一些建议。

--------------------------------------------------------------------------------

留意: ImageIcon 类 在创建时经由过程Swing自动应用一个MediaTracker下载图像数据。更多信息请参看 若何应用图标。

--------------------------------------------------------------------------------

应用MediaTracker下载图像并延迟显示图像

MediaTracker 类可以让你很轻易的下载一组图像并且获得图像被整个加载完的光阴。平日,图像数据在第一次显示时才被下载,为了哀求一组图像异步地被预先加载,应用下面的MediaTracker 一个措施: checkID(anInt, true) 或者 checkAll(true)。同时加载数据(等待数据到达)应用 waitForID 或者 waitForAll。 MediaTracker 的数据加载措施应用几个后台进程下载数据,这可以加快下载速率。

要反省图像加载的状态,你可以应用MediaTracker的 statusID 或者 statusAll 措施。仅仅想反省是否有图像还必要加载,可以应用 checkID 或者checkAll 措施。

MTImageSequenceTimer.java 是应用MediaTracker的 waitForAll 和checkAll 措施的一个修正版。在所有的图像完全加载前,这个applet只是简单的显示一个"Please wait..." 消息。参看 MediaTracker API 文档 得到一个急速绘制一个背景图像然则延迟绘制动画图像的典型。

下面是applet的截图:

这个图片是该applet的GUI。要运行那个applet,单击图片。该applet将在一个新浏览窗口显示。

下面是改变的代码,它应用一个 MediaTracker 赞助推迟图像的显示。不合的地方应用黑体标记了。

...//声明实例变量:

MediaTracker tracker;

tracker = new MediaTracker(this);

...//在init措施中:

for (int i = 1; i <= 10; i++) {

images[i-1] = getImage(getCodeBase(),

"images/duke/T"+i+".gif");

}

...//在buildUI 措施中,

//它被init和main措施调用,

//运容许我们将这个例子作为一个applet或者利用法度榜样运行:

for (int i = 1; i <= 10; i++) {

tracker.addImage(images[i-1], 0);

}

...//在actionPerformed 措施的开始:

try {

//开始下载图像,等待它们被加载完毕。

tracker.waitForAll();

} catch (InterruptedException e) {}

...//在paintComponent措施中:

//假如并非所有的图像被加载完,清除背景并显示状态字符串。

if (!tracker.checkAll()) {

g.clearRect(0, 0, d.width, d.height);

g.drawString("Please wait...", 0, d.height/2);

}

//假如所有的图像都被加载,绘制它们。

else {

...//same code as before...

}

加快图像的加载

无论你是否应用MediaTracker,应用URL加载图像(就像平日的applet所做的那样)平日要花费一段较长的光阴。这些光阴的大年夜多是初始化HTTP连接占用的,每一个图像文件必要一个零丁的HTTP连接,而每个连接可能花费几秒进行初始化。

避免这个机能丧掉的关键是将所有的图像放在一个文件里面。你可以应用一个JAR 文件做到这个,就像 将Applet的文件合并到一个文件里面 和 应用 JAR文件: 根基中描述的那样。

另一个可能有用的机能策略是将一组图像合并到一个图像文件中。可以到达这个目的的一个简单的措施是创建一个图像序列 --一个文件包孕一行图像。下面是一个图像序列的例子:

jack.gif:

要从一个图像序列中绘制一个图像,你首先必要将绘图区的大年夜小设置为一个图像的大年夜小。然后绘制图像序列,(假如需要的话)向左移位,是以在绘图区只有你想要显示的图像呈现。例如:

//imageStrip 是一个表示图像序列的Image工具。

//imageWidth 是序列中的一个零丁的图像的大年夜小。

//imageNumber 是要绘制的图像的序号(从0到numImages)

int stripWidth = imageStrip.getWidth(this);

int stripHeight = imageStrip.getHeight(this);

int imageWidth = stripWidth / numImages;

g.clipRect(0, 0, imageWidth, stripHeight);

g.drawImage(imageStrip, -imageNumber*imageWidth, 0, this);

假如你想图像加载得更快,你应该斟酌图像压缩规划,分外是帧内压缩。

您可能还会对下面的文章感兴趣: