テンプレートで学ぶJavaアプリのグラフィックの基本携帯アプリを作って学ぶJava文法の基礎(6)(2/3 ページ)

» 2008年06月16日 00時00分 公開
[緒方聡エスマテック株式会社]

匿名クラスの宣言の仕方

 CanvasクラスのJavadocをすべて見渡すと、abstractメソッドはたった1つで、そのためにわざわざクラス宣言をするのは面倒だ、しかもそのクラスから生成するインスタンスも1つしかない、というのが今回のケースに当たります。

 そういう場合に、匿名クラスがよく利用されます。匿名クラスは以下のように宣言します。

変数 = new クラス名 {
    クラスの内容
};

 そのクラスが抽象クラスなら、クラスの内容には抽象メソッドが実装されていなければなりません。

 さて、ソースに戻ってみると、Canvasクラスからcanvasというインスタンスを生成している個所では、Canvasクラスの抽象メソッドであるpaintメソッドが実装されていることが確認できますね。このクラスはCanvasクラスに機能を追加、または変更しているので、厳密にはCanvasクラスではなく、名前のない匿名クラスなのです。

コラム 「未実装? 空実装?」

Canvasクラスは「サブクラスでpaintメソッドを実装しなければならない」といっているのに、TemplateAppli.javaには、paintメソッド内部に「このメソッドは利用しないので実装する必要はない」というコメントがあり、相反すると読者は思うかもしれません。


クラスやメソッドが空っぽの状態でも、そのクラスやメソッドは「何もしない」ということをしている立派なクラスやメソッドなのですが、こういう「何もしない実装」というのを「空実装」と呼んでいて、第三者が見て「空実装」なのか「実装がまだ」なのか、が分かるように「実装する必要がない」旨をコメントで説明しています。


DoJaとMIDP別の“テンプレート詳細解説”

 プログラムは、どのような種類のものであれ、必ず目的を持っています。電卓アプリなら入力された数式を計算して結果を表示する目的を持っていますし、HelloWorldアプリですら、「hello, world」と表示する目的を持っています。

 ケータイJavaアプリも、それぞれに目的を持っています。目的が異なれば実装方法も異なることが多いのですが、ケータイJavaアプリでは、目的を達成するために必要な実装は共通化できるところが多いのです。

 ほぼすべてのケータイJavaアプリでは、以下の2つを行うでしょう。

  • 画面に何かを表示する
  • ユーザーからのキー入力を受け取る

 今回のテンプレートはまさにその目的を達成するための最小限の実装を提供しています。以下の説明はDoJaMIDPで読み分けてください。

iアプリの画面描画の方法【DoJa】

 今回のテンプレートでは、画面にデジタル時計を表示しています。その描画は、iアプリでは以下のように行っています。

41     void drawAll(Graphics g) {
42
        g.lock();
43
        Calendar calendar = Calendar.getInstance();
44
        String time = calendar.get(Calendar.HOUR_OF_DAY)+ ":";
45
        time += calendar.get(Calendar.MINUTE)+ ":";
46         time += calendar.get(Calendar.SECOND);
47
        g.setColor(Graphics.getColorOfName(Graphics.WHITE));
48
        g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
49
        g.setColor(Graphics.getColorOfName(Graphics.BLACK));
50
        g.drawString(time, 0,Font.getDefaultFont().getHeight());
51
        g.unlock(true);
52
    }

……【省略】……

55
    Graphics g = canvas.getGraphics();

 このテンプレートでは描画は1つのメソッドに集約されていて、そのメソッドは41行目で「drawAll」と名付けています。このメソッドはグラフィックスコンテキストを受け取り、受け取ったグラフィックスコンテキストに対して描画を行います。渡されるグラフィックスコンテキストは、55行目でキャンバスから取得しています。

 最初の42行目でグラフィックスコンテキストをロックし、最後の51行目でグラフィックスコンテキストをアンロックしています。これは「ダブルバッファリング」という、画面のちらつきを抑えるための“常とう手段”ともいえるテクニックです。51行目のunlockメソッドにtrueを渡すことによって、即座に描画を行います。この、ロックとアンロックによるダブルバッファリングは機種によってサポートされていない場合もあるので、「MIDPのように、自前でダブルバッファリングを行うのがよい」という考え方もあります。

 43行目から46行目は、カレンダーを使ってデジタル時計式の現在時刻文字列を作り出しているところです。コード量を抑えるために、けた数制御はしていません。

 47行目で、グラフィックスコンテキストに白を設定しています。これで、以降描画する際の色は白になります。48行目で、全画面を白に塗りつぶしています。49行目で、グラフィックスコンテキストに黒を設定しています。なお、MIDPでは白や黒などの基本的な色でさえあらかじめ用意されていません。そして、setColorメソッドはMIDPとは仕様が違うので、注意してください。

 このようにして、「全画面を背景色(この場合は、白)で塗りつぶして、新しい内容を一から描画する」というのもよく使われるテクニックです。

 50行目で作成したデジタル時計式の現在時刻文字列を描画しています。drawStringメソッドもMIDPとは仕様が違うので、注意してください。

MIDPアプリの画面描画の方法【MIDP】

 今回のテンプレートでは、画面にデジタル時計を表示しています。その描画は、MIDPでは以下のように行っています。

15     Image offi;

……【省略】……

18
    int COLOR_BLACK = 0x000000;
19
    int COLOR_WHITE = 0xFFFFFF;

……【省略】……

23
    public void paint(Graphics g) {
24
        g.drawImage(offi, 0, 0, Graphics.TOP | Graphics.LEFT);
25
    }

……【省略】……

33
    offi = Image.createImage(canvas.getWidth(), canvas.getHeight());

……【省略】……

52
    void drawAll(Graphics g) {
53
        Calendar calendar = Calendar.getInstance();
54
        String time = calendar.get(Calendar.HOUR_OF_DAY)+ ":";
55
        time += calendar.get(Calendar.MINUTE)+ ":";
56
        time += calendar.get(Calendar.SECOND);
57
        g.setColor(COLOR_WHITE);
58
        g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
59
        g.setColor(COLOR_BLACK);
60
        g.drawString(time, 0, 0, Graphics.TOP | Graphics.LEFT);
61
    }

……【省略】……

64
    Graphics g = offi.getGraphics();;

 このテンプレートでは描画は1つのメソッドに集約されていて、そのメソッドは62行目で「drawAll」と名付けています。このメソッドはグラフィックスコンテキストを受け取り、受け取ったグラフィックスコンテキストに対して描画を行います。渡されるグラフィックスコンテキストは、74行目でイメージから取得しています。このイメージは特別な呼び方をされるのですが、それについては後ほど説明します。

 MIDPではDoJaと違い、自前でダブルバッファリングを行わなければなりません。このダブルバッファリングの要が、15行目で定義してあるImageのインスタンスです。これは「オフスクリーンイメージ」と呼ばれ、複数の描画処理をまとめて行うためのバッファです。オフスクリーンイメージを使用したダブルバッファリングは、画面のちらつきを抑えるための“常とう手段”ともいえるテクニックです。

 18行目と19行目は、色を定義しています。ここでは、16進数で色を表しています。なおDoJaでは、白や黒などの基本的な色はあらかじめ“定数”で用意されています

 33行目では、キャンバスと同じ縦横サイズでオフスクリーンイメージを生成しています。

 63行目から66行目は、カレンダーを使ってデジタル時計式の現在時刻文字列を作り出しているところです。コード量を抑えるために、けた数制御はしていません。

 67行目で、グラフィックスコンテキストに白を設定しています。これで、以降描画する際の色は白になります。68行目で、全画面を白に塗りつぶしています。69行目で、グラフィックスコンテキストに黒を設定しています。setColorメソッドはDoJaとは仕様が違うので、注意してください。

 このようにして全画面を背景色(この場合は、白)で塗りつぶして、新しい内容を一から描画する、というのもよく使われるテクニックです。

 70行目で作成したデジタル時計式の現在時刻文字列を描画しています。drawStringメソッドはDoJaとは仕様が違うので、注意してください。

iアプリの再描画の方法【DoJa】

 描画処理自体はdrawAllメソッドで行っているのですが、このメソッドが定期的に呼び出されなければ、デジタル時計として成り立ちません。DoJaでは、どのように再描画を行っているのかを見てみましょう。

54     public void run() {
55         Graphics g = canvas.getGraphics();
56         while (true) {
57             drawAll(g);
58             sleep(100);
59         }
60     }
61
62     void sleep(long millis) {
63         try {
64
            Thread.sleep(millis);
65
        } catch (InterruptedException e) {
66
            // 例外は発生しない
67
        }
68
    }

 DoJaでは、2つのメソッドで実現しています。

 1つ目は54行目のrunメソッドです。これは「スレッド」という複数の処理を並行して行うための仕組みで使用されるメソッドで、TemplateAppliがimplementsしているRunnableインターフェイスで必須のメソッドです。スレッドについては、次回じっくり解説するので、いまは「並行してプログラムを処理できる仕組み」ぐらいに理解しておいてください。

 55行目はグラフィックスコンテキストをキャンバスから取得している個所でした。

 56行目はwhileループです。条件がtrueなので、無限ループですね。

 57行目でdrawAllメソッドを呼び出しています。この呼び出しは無限ループ内なので、再描画が定期的に行われることが分かると思います。

 ただしこのままでは、この無限ループが休みなく処理を続けてしまい、プログラムの制御ができなくなってしまいます。そのため、58行目で100ミリ秒だけスリープしています。

 スリープ処理を行うメソッドは62行目にあり、処理はThreadクラスsleepメソッドを呼び出しているだけです。ここには、初めて見るキーワードがいくつかありますが、これらのキーワードを使って「例外」の処理をしています。例外処理についても、次回にスレッドと一緒に解説するので、いまは「例外というものと例外処理というものがある」ぐらいに理解しておいてください。

 スリープする時間は、作りたいアプリの種類に応じて調整するとよいでしょう。この100ミリ秒というのは1秒当たり10回画面を再描画する、ということになります。

MIDPアプリの再描画の方法【MIDP】

 描画処理自体はdrawAllメソッドで行っているのですが、このメソッドが定期的に呼び出されなければ、デジタル時計として成り立ちません。MIDPでは、どのように再描画を行っているのかを見てみましょう。

63     public void run() {
64         Graphics g = offi.getGraphics();
65         while (true) {
66             drawAll(g);
67             canvas.repaint();
68             canvas.serviceRepaints();
69             sleep(100);
70         }
71     }
72
73     void sleep(long millis) {
74         try {
75             Thread.sleep(millis);
76         } catch (InterruptedException e) {
77             // 例外は発生しない
78         }
79     }

 MIDPでは2つのメソッドで実現しています。

 1つ目は75行目のrunメソッドです。これは「スレッド」という複数の処理を並行して行うための仕組みで使用されるメソッドで、TemplateMIDletがimplementsしているRunnableインターフェイスで必須のメソッドです。スレッドについては、次回じっくり解説するので、いまは「並行してプログラムを処理できる仕組み」ぐらいに理解しておいてください。

 76行目はグラフィックスコンテキストをオフスクリーンイメージから取得している個所でした。

 77行目はwhileループです。条件がtrueなので、無限ループですね。

 78行目でdrawAllメソッドを呼び出しています。この呼び出しは無限ループ内なので、再描画が定期的に行われることが分かると思います。

 ただしこのままでは、この無限ループが休みなく処理を続けてしまい、プログラムの制御ができなくなってしまいます。そのため、79行目で100ミリ秒だけスリープしています。

 スリープ処理を行うメソッドは83行目にあり、処理はThreadクラスsleepメソッドを呼び出しているだけです。ここには初めて見るキーワードがいくつかありますが、これらのキーワードを使って「例外」の処理をしています。例外処理についても、次回にスレッドと一緒に解説するので、いまは「例外というものと例外処理というものがある」ぐらいに理解しておいてください。

 スリープする時間は、作りたいアプリの種類に応じて調整するとよいでしょう。この100ミリ秒というのは1秒当たり10回画面を再描画する、ということになります。

 次ページでは、DoJaとMIDPのCanvasクラスの動き方の違いについて説明し、最後に移植性を高めるためのヒントも教えます。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。