import java.awt.*; import javax.swing.*; import java.awt.event.*; class SimpleAnime extends JFrame { private void init() { this.setTitle("SimpleAnime"); this.setSize(300,200); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { SimpleAnime frame = new SimpleAnime(); frame.init(); } }
import java.awt.*; import javax.swing.*; import java.awt.event.*; class SimpleAnime extends JFrame { JPanel panel; Graphics g; private void init() { this.setTitle("SimpleAnime"); this.setSize(300,200); panel = new JPanel(); this.getContentPane().add(panel); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); g=panel.getGraphics(); g.setColor(Color.blue); int x=0, xdelta=10; while(true) { g.fillOval(x,80,50,50); try{Thread.sleep(50);}catch(Exception e){} g.clearRect(x, 80, 52,52); x+=xdelta; if(x>250) xdelta=-10; if(x<0) xdelta=10; } } public static void main(String[] args) { SimpleAnime frame = new SimpleAnime(); frame.init(); } }
Javaでは次のようにしてマルチスレッドを実現します
例えば以下のプログラムではメインとサブのスレッドが別個にに数字を表示していきます。
import java.lang.Thread; class SubThread implements Runnable { public void run() { int i=0; while(true) { System.out.println("this is sub:" + i++); try {Thread.sleep(1000);}catch(Exception e){} } } } class ThreadTest { public static void main(String[] args) { SubThread sub=new SubThread(); new Thread(sub).start(); for(int i=0;;i++) { System.out.println("this is main:" + i); try {Thread.sleep(2000);}catch(Exception e){} } } }
上記のボールを動かすプログラムでは、main()で、frame.init()したあと、このメソッドで無限にアニメーション書き換えを行うことになります。なので、二度とmain()には戻ってきません。(以下で示した、メニューは、また別のスレッドで動くので、このままでも動きます)
アニメーションだけをするなら、これでも良いのですが、他にも仕事をしたい場合には難しいですし、やれないことはないですが、タイミングを計るのが難しいです。ということで、アニメーションする部分は、別のスレッドにして、そちらに任せてしまうのが通常です。
以下のように、別のインスタンスを別スレッドで動かします。別スレッドで動かすインスタンスを作るために、Animatorという名前のクラスを用意しました。別スレッドで動かすためには、Runnableをimplementする必要があります。ここで必須のrunというメソッドが、裏で実行されるので、そこに、上記のプログラムのアニメーション描画部分をそっくり移動させます。Graphics gの情報を伝えておく必要があるので、それを設定するメソッドも作りました。アニメーションしつつ、main()の方で数字を表示しています。
import java.awt.*; import javax.swing.*; import java.awt.event.*; import java.lang.Thread; class Animator implements Runnable { Graphics g; public void setGraphics(Graphics animeG) { g=animeG; } public void run() { int x=0, xdelta=10; while(true) { g.fillOval(x,80,50,50); try{Thread.sleep(50);}catch(Exception e){} g.clearRect(x, 80, 52,52); x+=xdelta; if(x>250) xdelta=-10; if(x<0) xdelta=10; } } } class SimpleAnime extends JFrame { JPanel panel; Graphics g; Animator animator; private void init() { animator=new Animator(); this.setTitle("SimpleAnime"); this.setSize(300,200); panel = new JPanel(); this.getContentPane().add(panel); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); g=panel.getGraphics(); g.setColor(Color.blue); animator.setGraphics(g); new Thread(animator).start(); } public static void main(String[] args) { SimpleAnime frame = new SimpleAnime(); frame.init(); for(int i=0;;i++) { System.out.println(i); try {Thread.sleep(500);}catch(Exception e){} } } }
上記のプログラムにメニューを追加して、 ボールの色と速さをメニューで指定するようにしました。 メニューはメインのJFrameインスタンスで作ってこれに貼りつけていますが、 Action Listenerは、別スレッドで動いているAnimatorクラスのインスタンスとしました。
import java.awt.*; import javax.swing.*; import java.awt.event.*; import java.lang.Thread; class Animator implements Runnable, ActionListener { Graphics g; int xdelta =5; public void setGraphics(Graphics animeG) { g=animeG; } public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); if(command !=null) { System.out.println(command); } if(command=="red") g.setColor(Color.red); if(command=="blue") g.setColor(Color.blue); if(command=="yellow") g.setColor(Color.yellow); if(command=="fast") if(xdelta>0) xdelta=30; else xdelta=-30; if(command=="slow") if(xdelta>0) xdelta=5; else xdelta=-5; } public void run() { int x=0; while(true) { g.fillOval(x,80,50,50); try{Thread.sleep(50);}catch(Exception e){} g.clearRect(x, 80, 52,52); x+=xdelta; if(x>250) xdelta=-xdelta; if(x<0) xdelta=-xdelta; } } } class SimpleAnime extends JFrame { JPanel panel; Graphics g; Animator animator; private void makeMenu() { JMenuBar menubar = new JMenuBar(); JMenu menu = new JMenu("color"); JMenu menuSpeed = new JMenu("speed"); JMenuItem item1 = new JMenuItem("red"); item1.addActionListener(animator); item1.setActionCommand("red"); JMenuItem item2 = new JMenuItem("blue"); item2.addActionListener(animator); item2.setActionCommand("blue"); JMenuItem item3 = new JMenuItem("yellow"); item3.addActionListener(animator); item3.setActionCommand("yellow"); menu.add(item1); menu.add(item2); menu.add(item3); JMenuItem item4 = new JMenuItem("fast"); item4.addActionListener(animator); item4.setActionCommand("fast"); JMenuItem item5 = new JMenuItem("slow"); item5.addActionListener(animator); item5.setActionCommand("slow"); menuSpeed.add(item4); menuSpeed.add(item5); menubar.add(menu); menubar.add(menuSpeed); this.setJMenuBar(menubar); } private void init() { animator = new Animator(); this.setTitle("SimpleAnime"); this.setSize(300,200); this.makeMenu(); panel = new JPanel(); this.getContentPane().add(panel); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); g=panel.getGraphics(); g.setColor(Color.blue); animator.setGraphics(g); new Thread(animator).start(); } public static void main(String[] args) { SimpleAnime frame = new SimpleAnime(); frame.init(); for(int i=0;;i++) { System.out.println(i); try {Thread.sleep(500);}catch(Exception e){} } } }
アニメーションの途中で、ボールがちらつくことがあります。 ボールの場所を矩形で消して、新しいボールを描いているので、 その途中の作業が見えてしまうからです。 これを無くすには、ダブルバッファの手法を用います。 すなわち、描画する面をもう一枚用意して、 そちらに描画し、 描画が終わったところで、一気に更新する方法です。
ダブルバッファの手法は、授業の最終課題である「お絵かきプログラム」のところで説明します。