アニメーション


 JavaのGUIで本格的なアニメーションを作る機会はあまり無いかもしれません。
アニメーションなどのマルチメディアな処理はやはり、C、C++のようなネイティブコードのほうに
一日の長があるからです。(JavaからDirectXが使えないのも大きな理由ですが…。)

しかし、たとえば、Webシステムにおいて、巨大なファイル転送や、大きなバッチ処理などを投入したときに
ユーザーになにも進捗状況が伝わらないのでは、少々UIに問題がある場合もあるでしょう。
そういった時に、JavaAppletで簡易なアニメーションを表示させるだけでも、ユーザーにとっては

『ああ、動いているんだな…』

と、安心感を与えるとともに、むやみやたらと実行を押されてしまうことを抑止する相乗効果も期待できます。
ここでは、そんな簡単なアニメーションのサンプルをご紹介しましょう。

アニメーションの仕組み

アニメーションは画像を高速で切り替えることによる残像現象を利用し、動いているようにみせる技術です。
最近のテレビゲームでは、秒間に60回も描きかえて滑らかなアニメーションを表しているものもありますが、
ここではそこまでのものは求めないこととします。秒間1コマ程度のパラパラまんがでも動いていることを、
知らせるには十分です。
(秒間60コマを表現させるためには高精度なタイマーと、高速な描画処理が必要ですので…)

下記に1つのバグを含むサンプルコードを示します。(バ グが見つけられる方にはこのTips自体不要かもしれません)


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.util.Iterator;
import javax.imageio.*;
import javax.imageio.stream.*;



public class JDsamp extends JPanel {
public static void main(String[] args){
JDialog jd = new JDialog();
jd.setSize(new Dimension(430, 150));
jd.getContentPane().add(new JLabel("サンプルアニメーション"),BorderLayout.NORTH);
JDsamp j = new JDsamp();
jd.getContentPane().add(j,BorderLayout.CENTER);
jd.setResizable(false);
jd.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
jd.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
jd.show();

j.timer.start();
}

int timeInterval = 1000;
Timer timer;
private int xpos=0;
private Image img;;

public JDsamp(){
this.setLayout(null);
loadImage();
timer = new Timer(timeInterval, new ActionListener(){
public void actionPerformed(ActionEvent e){
repaint();
}
});
}
private void loadImage()
{
Iterator readers = ImageIO.getImageReadersBySuffix("gif");
if (readers.hasNext()) {
ImageReader reader = (ImageReader)readers.next();
try {
ImageInputStream stream = ImageIO.createImageInputStream(new File("duke_jdk.gif"));
reader.setInput(stream);
img = reader.read(0);
reader.dispose();
}catch (IOException ex) {
ex.printStackTrace();
System.exit(-1);
}
}
}

public void paint(Graphics g){
g.drawImage(img,xpos,0,this);
if(xpos + 83 +83 > 420 ) xpos = 0;
else xpos+=83;
}
}
このコードを実行してみます
(実行するにはClassカレントパスに"duke_jdk.gif"が必要です。お好きな画像で試したい場合は、上記コードのファイル名と、
その4行上のgetImageReadersBySuffix を修正してください。標準で読み込める形式はjpg, gif, png などです。)

実行してみると、思った通りになりません。(下図)
イメージ1

イメージ2

イメージ3

なぜでしょうか?
各コマを見た感じでは、どうやら前 回の画像が残っているように見えますね。
書き換える前に、一度クリアする必要があるようです。
クリアーを追加してみましょう。
	public void paint(Graphics g){
g.crearRect(0,0,this.getWidth(),this.getHeight());
g.drawImage(img,xpos,0,this);
if(xpos + 83 +83 > 420 ) xpos = 0;
else xpos+=83;
}

CLイメージ

CLイメージ2

CLイメージ3

 ゴミは消えるようになりましたが、なにか違和感がありますね。どうやら、背景色がおかしいようで す。
GraphicsクラスのclearRectのJavaDocには、

 指定された矩形を、現在の描画表面のバックグラウンドカラーで塗りつぶすことに よりクリアします。
 この操作は、現在のペイントモードを使いません。
 Java 1.1 以降は、オフスクリーンのイメージのバックグラウンドカラーはシステムにより異なります。
 アプリケーションは setColor に続けて fillRect を使うことによって、
 オフスクリーンイメージをクリアして特定の色にすることを保証します。

とあります。この「システムによる異なります」というフレーズが問題のようです。
そこで、このドキュメントにあるように、背景色をセットしてからfillRect してみましょう。

	public void paint(Graphics g){
g.setColor(this.getBackground());
g.fillRect(0,0,this.getWidth(),this.getHeight());
g.drawImage(img,xpos,0,this);
if(xpos + 83 +83 > 420 ) xpos = 0;
else xpos+=83;
}
FILL1

FILL2

FILL3

 うまくいきましたね。ここでは、領域のクリアを独自で実装してみましたが、もう一つ別のアプローチもあります。
このJDsampクラスはスーパークラスとしてJPanelを継承しています。このスーパークラスの再描画時にクリアする処 理
行っていますので、paintメソッドを下記のように変更しても同様の動作になります。
	public void paint(Graphics g){
super.paint(g);
g.drawImage(img,xpos,0,this);
if(xpos + 83 +83 > 420 ) xpos = 0;
else xpos+=83;
}


ブラウザのBACKでお戻り下さい