BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル JavaFX 2.0 - Javaによるリッチクライアント基盤 (後編)

JavaFX 2.0 - Javaによるリッチクライアント基盤 (後編)

ブックマーク

JavaFX 2.0の機能

前編に引き続き、JavaFX 2.0の代表的な機能を紹介していきましょう。前回、示した代表的機能を再掲します。

  • Prismグラフィックエンジン
  • シーングラフ
  • UIコントロール
  • メディア
  • CSS
  • バインド
  • アニメーション
  • エフェクト
  • 非同期処理
  • Swingとの連携

前編ではUIコントロールまでを紹介したので、後編でメディア以降の機能について説明していきます。

メディア

Javaが弱い分野の1つにメディアがあります。

サウンドに関してはJava Sound、ムービーはJava Media Framework (JMF)がありますが、どちらもここ数年はまったく更新されていません。そこで、JavaFX 1.xではOn2 Technologiesと提携し、メディア関連APIの強化を行いました。

しかし、その後On2 TechnologiesはGoogleに買収されてしまったのです。このため、JavaFX 2.0ではメディア関連APIを白紙の状態から作り直しを余儀なくされてしまいました。

現状、JavaFXがサポートしているメディアのフォーマットを以下に示します。

  • サウンド: MP3、AIFF (Uncompressed PCM)、WAV (Uncompressed PCM)
  • ムービー: FLV (VP6, MP3)

JavaFX 2.0のメディア関連APIは、JavaFX 1.xの使い方を引き継いでいます。メディアを表すのがMediaクラス、メディアを再生するのがMediaPlayerクラスです。

サウンドはMediaPlayerクラスだけで再生可能ですが、ムービーの場合表示を行なうMediaViewクラスを使用します。いずれもjavafx.scene.mediaパッケージで定義されています。

        // サウンドの再生
        String soundURL = ...;   
        Media audioMedia = new Media(soundURL);
        MediaPlayer audioPlayer = new MediaPlayer(audioMedia);
        audioPlayer.play();
 
        // ムービーの再生
        String movieURL = ...;
        Media movieMedia = new Media(movieURL);
        MediaPlayer moviePlayer = new MediaPlayer(movieMedia);        
        MediaView movieView = new MediaView(moviePlayer);
        moviePlayer.play();

MediaクラスのコンストラクタにはメディアのURLを指定します。現状、サポートしているプロトコルはhttp:とfile:の2種類です。

MediaPlayerクラス以外に、ゲームの効果音などに使用できるAudioClipクラスも提供されています。

        // オーディオクリップの再生
        String soundURL = ...;   
        AudioClip clip = new AudioClip(soundURL);
        clip.play();

現状ではサポートされているコーディックが少ない、ストリーミングに対応していないなど、機能的にはまだ不十分なのですが、今後拡充されていく予定です。

CSS

HTMLで見栄えを定義するために使用されるCSSですが、HTMLだけでなくリッチクライアントでも見栄えを変更するために使用することができます。JavaFXでも1.xの頃からCSSによって描画を変化させることが可能です。

CSSを使用するには、HTMLと同様にJavaFXでもスタイルを埋めこむ手法と、スタイルシートファイルを読み込む手法があります。ここではよく使用されるファイルとして読み込む方法を紹介します。

スタイルシートはSceneオブジェクトに対してセットします。

        Scene scene = new Scene(container, 400, 100);
        // スタイルシートの設定
        scene.getStylesheets().add("/style.css");
 
        Button button1 = new Button("Button1");
        Button button2 = new Button("Button2");
        Button button3 = new Button("Button3");
        // IDを設定する
        button3.setId("button3");

CSSは次のように記述します。

.button {
    -fx-font: 14pt "SansSerif";
    -fx-text-fill: #006666;
    -fx-background-color: orange;
    -fx-border-radius: 20;
    -fx-background-radius: 20;
    -fx-padding: 5;
}
 
#button3 {
    -fx-font: 14pt "Monospaced";
    -fx-text-fill: #0066DD;
    -fx-background-color: red;
    -fx-border-radius: 20;
    -fx-background-radius: 20;
    -fx-padding: 5;
}

Buttonクラスなどの描画要素はCSSのクラスとして扱われます。CSSのIDを使用したい場合はsetIdメソッドでIDを指定しておきます。ここではButton3だけ違ったスタイルにしています。

図1にCSSによる描画の変化を示しました。

CSSによる描画の変化

CSSによる描画の変化
図1 CSSによる描画の変化

 

バインド

前回、紹介したようにバインドは変数同士を自動的に同期させるための機構です。

JavaFX Scriptではバインドを言語レベルでサポートしていました。しかし、JavaFX 2.0ではJavaの言語仕様を変更できないため、APIでバインドを実現しています。

バインドには高レベルAPIと低レベルAPIがあります。高レベルAPIは簡単に使えますが、複雑な同期には使いづらく、またパフォーマンスも低レベルAPIに比べると劣っています。低レベルAPIは逆にパフォーマンスが高く、複雑な同期も可能ですが、記述が長くなってしまいます。

JavaFXのクラスのメソッドを見てみると、メソッド名がPropertyで終っているメソッドがあります。たとえば、四角を表すRectangleクラスではxPropertyメソッドやyPropertyメソッドが定義されています。これらのメソッドで取得できるプロパティがバインドの対象となります。

高レベルAPIを使用したバインドは次のように記述します。ここでは四角のx座標と、スライダの値をバインドさせています。

        Rectangle rect = new Rectangle(50, 10, 50, 30); 
        Slider slider = new Slider(50, 300, 0);

        // 四角のx座標とスライダの値をバインドさせる
        rect.xProperty().bind(slider.valueProperty());

スライダの値を変化させると四角のx座標が自動的に同期します。これにより、図2に示したように四角が移動します。

 バインドによる四角とスライダの同期

バインドによる四角とスライダの同期
図2 バインドによる四角とスライダの同期

低レベルAPIではBindingインタフェースを使用します。実際にバインドする場合は、Bindingインタフェースの実装クラスがプロパティの型に応じた提供されているので、これらのクラスを使用します。

たとえば、四角の色をRGBを表す3つのスライダの値とバインドしてみます。色はColorクラスで表すので、ObjectBindクラスを使用してバインドを行ないます。

        Rectangle rect = new Rectangle(50, 10, 300, 70);

        final Slider red = new Slider(0, 254, 254);
        final Slider green = new Slider(0, 254, 254);
        final Slider blue = new Slider(0, 254, 254);

        ObjectBinding<Color> binding = new ObjectBinding<Color>() {
            {
                // スライダの値とバインドする
                super.bind(red.valueProperty());
                super.bind(green.valueProperty());
                super.bind(blue.valueProperty());
            }

            @Override
            protected Color computeValue() {
                // スライダの値が変更されたら、
                // computeValueメソッドがコールされるので
                // Colorオブジェクトを生成して返す              
                return Color.rgb((int)red.getValue(),
                                 (int)green.getValue(),
                                 (int)blue.getValue());
            }
        };
        
        // 四角の色とbindingをバインドする
        rect.fillProperty().bind(binding);

3個のスライダのいずれでも値を変更するだけで、図3に示したように四角の色が変化します。

低レベルAPIによるバインドの例 
図3 低レベルAPIによるバインドの例

 

アニメーション

アニメーションは使いやすいUIを構成する上で欠かせない機能です。もちろん、JavaFXでも、アニメーションは重要な機能の1つです。

アニメーションは時間軸を設定し、決められたタイミングで描画要素の状態を変化させたり、処理を行なわせることで実現します。JavaFXでは時間軸をTimelineクラス、決められたタイミングで行なう処理をKeyFrameクラスで表します。

KeyFrameクラスは、オブジェクトの状態を変化させるためのKeyValueクラス、処理を記述するEventHandlerインタフェースを用いてオブジェクトを生成します。

アニメーションをスタートさせるにはTimelineクラスのplayメソッドをコールします。

        rect = new Rectangle(10, 60, 50, 30);
 
        timeline = new Timeline();
        timeline.getKeyFrames().addAll(
            new KeyFrame(Duration.ZERO, // 初期位置を設定
                         new KeyValue<Number>(rect.translateXProperty(), 0.0)),
            new KeyFrame(new Duration(1000), // 1秒後の移動量を設定
                         new KeyValue<Number>(rect.translateXProperty(), 320.0)));
timeline.play();

この例では、四角をx軸方向に移動させるアニメーションを実行します。

Timelineクラスを使用することでアニメーションは記述できますが、記述は少々煩雑です。そこで、移動やフェードなどよく使用されるアニメーションを簡単に記述するためのTransitionクラスも提供されています。移動はTranslateTransitionクラス、フェードであればFadeTransitionクラスを使用します。

たとえば、円を2秒間かけて非透明から透明にする場合は次のように記述します。

        Circle circle = new Circle(100, 100, 50);
 
        FadeTransition transition 
            = new FadeTransition(new Duration(2000), circle);
        transition.setFromValue(1.0);
        transition.setToValue(0.0);
 
        transition.play();

図4は複数の円をフェードイン、フェードアウトさせているサンプルアプリケーションです。サンプルは公開していますので、ぜひ試してみてください。

フェードの例
図4 フェードの例

 

エフェクト

描画要素に対して影をつけたり、ぼかしたりする効果をエフェクトと呼びます。エフェクトもUIではなくてはならない要素の1つです。

しかし、SwingではエフェクトをかけるためにJava 2Dを使わなくてはならず、記述が冗長になってしまいました。それに対しJavaFXでは簡潔に記述することが可能です。また、コントロールやイメージ、四角や円などさまざまな描画要素に対しても、すべて同一の手順でエフェクトを付加することができます。

たとえば、イメージに対して、明るくするエフェクトを施す場合、次のように記述します。

        String imageURL = ...;
        image = new ImageView(new Image(imageURL));
        image.setEffect(new Bloom());

setEffectメソッドの引数の型はEffectクラスです。エフェクトに応じてEffectクラスのサブクラスが提供されており、ここでは明るくするエフェクトを行なうBloomクラスを使用しています。その他の代表的なエフェクトを図5に示しました。

代表的なエフェクト
図5 代表的なエフェクト

 

非同期処理

UIの処理は通常シングルスレッドで行ないます。これはAWTでもSwingでも同じです。しかし、イメージのロードなど描画に比べて時間がかかる処理を行なうと、UIの処理がストップしてしまいます。

そこで、時間のかかる処理は、UIのスレッドとは別のスレッドでバックグラウンドで非同期で行なうようにします。たとえば、SwingではSwingWorkerクラスでバックグランド処理を記述することができます。

JavaFXの場合、バックグラウンドで非同期に行なう処理はTaskクラスで記述します。Taskクラスのexecuteメソッドがバックグラウンドで処理されます。処理の結果はgetメソッドで取得することができます。

たとえば、イメージのロードをバックグラウンドで行なう場合、次のように記述します。ただし、Imageクラスにはバックグラウンドでイメージをロードする機能があるので、通常はそちらを使用します。

        view = new ImageView();

        task = new Task<Image>() {
            @Override
            protected Image execute() {
                // バックグラウンドで行なう処理            
                String imageURL = ...;
                return new Image(imageURL);
            }
        };
        task.setOnDone(new EventHandler<TaskEvent>() {
            // executeメソッドが完了したらコールされる
            public void handle(TaskEvent event) {
                Image image = task.get();
                view.setImage(image);
            }
        });
        // バックグラウンド処理の開始
        task.start();

イメージのロードなどのI/O処理は描画に比べると時間がかかります。こういう場合でも処理をバックグラウンドで行なうことでUIの応答速度を損なうことなく処理を行なうことができます。また、時間を要する計算などもバックグラウンドで行なうべきです。

JavaFXでは、バックグラウンド処理を簡単に記述できるので、ぜひ活用したいところです。

なお、上記のコードではTaskクラスを使用していますが、JavaFX
2.0の正式版ではTaskクラスは変更される予定になっています。しかし、同じように非同期処理を扱えるようになるはずです。

Swingとの連携

Javaの標準のUIライブラリであるSwingとJavaFXを連携させることも、JavaFX 2.0では可能です。

連携を考える場合、SwingのUIにJavaFXを埋めこむことと、JavaFXのUIにSwingを埋めこむことの両方が考えられます。しかし、JavaFXでは前者、つまりSwingのUIにJavaFXを埋めこむことだけがサポートされます。

SwingにJavaFXのシーングラフを埋めこむために、Swingのコンポーネントであり、かつシーングラフのためのコンテナとなるJFXPanelクラスが提供されています。

public class SwingJavaFXDemo extends Application {
    // シーングラフを貼るSwingコンポーネント
    private JFXPanel jfxPanel;
 
    // initSwingメソッドはSwingのスレッドで処理する
    public void initSwing() {
        JFrame frame = new JFrame("Swing-JavaFX Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300, 100);
 
        // シーングラフを貼るコンポーネントを生成
        jfxPanel = new JFXPanel();
        jfxPanel.setPreferredSize(new Dimension(280, 80));
        frame.add(jfxPanel);
 
        frame.setVisible(true);
    }
 
    // startメソッドはJavaFXのスレッドで処理する
    @Override
    public void start(Stage stage) {
        try {
            // Swingのスレッドで処理する
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    initSwing();
                }
            });
        } catch (InvocationTargetException ex) {
            return;
        } catch (InterruptedException ex) {
            return;
        }
 
        FlowPane container = new FlowPane(10, 10);
        container.setAlignment(Pos.CENTER);
        Scene scene = new Scene(container);
 
        Label label = new Label("Label");
        TextBox textBox = new TextBox("Text Box");
        Button button = new Button("Button");
        container.getChildren().addAll(label, textBox, button);
 
        // Swingにシーングラフを貼る
        jfxPanel.setScene(scene);
    }
 
    public static void main(String[] args) {
        // JavaFXのスレッドを起動
        Application.launch(SwingJavaFXDemo.class, null);
    } }

注意すべき点として、SwingとJavaFXでは処理のためのスレッドが異なるということがあげられます。SwingのスレッドではJavaFXにはアクセスせず、またJavaFXのスレッドではSwingにアクセスしないようにします。

比較のため、図6にSwingのコンポーネントと、JavaFXのUIコントロールを並べてみました。見た目がずいぶん違うことが分かります。

SwingとJavaFXの連携
図6 SwingとJavaFXの連携
 

ここまで、JavaFX 2.0の機能を見てきましたが、これ以外にもシーングラフを記述しやすくするビルダー、HTTP通信、JSONパーサーなど多くの機能を提供しています。

Java以外の言語からJavaFXを使用する

今までは、JavaFXの機能を紹介してきました。その一方、JavaFXがJavaのライブラリになったことによる利点もあります。その1つが、Java VM上で動作する多くの言語でJavaFXが扱えるようになったことです。

Groovy、JRuby、ScalaなどJava VM上で動作する言語は多くあります。これらの言語の多くは、Javaのライブラリを扱えるようになっています。このため、JavaFXも使用できるようになったのです。

たとえば、Groovyを使用してHello, World!を記述してみます。

class Hello extends Application {
      void start(Stage stage) {
        def scene = new Scene(
            new Group(
                new Label(
                    text: "Hello, World!",
                    font: new Font(20),
                )
            ), 100, 30);

        stage.scene = scene;
        stage.visible = true;
    }
 
    static main(args) {
        Application.launch(Hello.class, null);
    } }

GroovyはJava Beansのプロパティをプロパティ名: 値のように記述することができます。これを利用することで、JavaFX Scriptのように宣言的にUIの構造を表すことが可能です。

さらに、GroovyのBuilderという機能を使用すると、より宣言的に記述することも可能です。

 

リリースに向けて

JavaFX 2.0はリリースまでに3つの段階が設定されています。現在は、一般向けのベータが公開されていますが、その前の段階としてパートナー向けに公開されていました。その後、2011年の第3四半期にリリースが予定されています。

すでに、予定していた機能の大部分は実装済みであり、現在は品質の向上を図っています。

ロードマップについての詳細はJavaFX 2.0のロードマップにありますので、ぜひご参照ください。

JavaFX 2.0はJavaFX Scriptを捨て、Javaのライブラリとして生まれ変わりました。これが吉と出るか、凶と出るかはまだ分かりません。少なくともJavaFX 1.3、もしくはSwingの開発者であれば、JavaFX 2.0に移行するのは容易です。

また、マルチメディア対応など既存のJavaにはない機能をJavaFX 2.0は多く持っています。これらの機能を使うことで、効率的にリッチクライアントを構築していくことが可能です。

その一方、リッチクライアントを構築するのに重要なデザイン面でのワークフローがまだ確立していないのも事実です。少なくともデザイナーが使用できるツールが不可欠です。これに対してOracleがどのような方策を取ってくるのか、注目していきたいと思います。

なお、本記事で使用したサンプルはすべてGitHubInfoQJavaFXDemoリポジトリよりダウンロードすることができます。サンプルは文字コードがUTF-8となっています。そのため、Windowsのコマンドプロンプトからコンパイルする場合、javacのオプションとして
-encoding UTF-8 のように文字コードを指定してください。
 

 

この記事に星をつける

おすすめ度
スタイル

BT