Hatena::ブログ(Diary)

hd 4.0 RSSフィード

2014-03-20

ラムダはじめました

Java8 出ましたねー。ほんとに出るとは思いませんでした。ダウンロードは以下から。

Java8 は Java5 以来となる大きな文法の変更や機能拡張が追加されていますが、まず目を引くのはラムダでしょう。というわけで JDK と NetBeans8 を入れてちょろっと試してみました。 EA 版とかは触ってなかったので、今回が Java8 デビューです。

対象にしたのは個人的に JavaFX でよく使うコードの中でもなかなかアレな部類に入ると思われる、 TableColumnプロパティをバインドする setCellValueFactory にしました。こんなモデルクラスがあるとして、

public class FooModel {

    private final StringProperty barProperty = new SimpleStringProperty();

    public StringProperty barProperty() {
        return barProperty;
    }
}

Java7 版だとこんな感じ。だいぶアレですね。単にバインドしたいだけなのにこのコード量。やりたいことが激しく埋もれてる感があります。

        column.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<FooModel, String>, ObservableValue<String>>() {
            @Override
            public ObservableValue<String> call(TableColumn.CellDataFeatures<FooModel, String> param) {
                return param.getValue().barProperty();
            }
        });

Java8 + NetBeans8 では上記のコードに警告が出るので、おもむろに Alt+Enter で Use Lambda Expression を選択すると以下の様な感じになります。すっきり。

        column.setCellValueFactory((TableColumn.CellDataFeatures<FooModel, String> param) -> param.getValue().barProperty());

まだうるさい型が残ってますね。ラムダ式引数の型は省略できるので、型宣言を消すことでさらに以下のようにできます。原型を留めないほどすっきりしました。やりたいことが明確になってよいですね。

        column.setCellValueFactory(param -> param.getValue().barProperty());

なお、 NetBeans では Refactor -> Inspect and Transform から、 Java のバージョンを選択してプロジェクト単位やファイル単位などでまとめて文法のマイグレーションチェック及び実行ができます。これは便利。

2013-12-09

JavaFX でページめくりアニメーション的ななにか

この記事は JavaFX Advent Calendar 2013 - Adventar の 9 日目です。昨日は @kokuzawa さんの IntelliJ IDEA 13で作るJavaFXアプリケーション - Katsumi Kokuzawa’s Blog でした。

JavaFX で、本のページをめくるようなアニメーションが欲しいなーと思ったので作ってみました。めくりということで 3 日目の記事 JavaFXでめくるエフェクト!! | るーつにゃんブログ とややかぶり気味になってしまいましたがご容赦をm(__)m

まず完成形から。こんな感じです。

・・・はい、まぁそこらの Web コミックのアニメーションあたりにはいくらか近づけたでしょうか・・。左からのページめくりを延々と繰り返す感じです。ページをいっぱい用意するのがめんどくさったので 1 〜 4 ページを使いまわしてます。

「ページめくりなんだから画像を立体方向に回転させればそれっぽくなるだろ」と安直に考えたので、基本的には ImageView を Y 軸中心に回転させているだけです。とりあえず左ページを開くアニメーションの動作について説明します。

FXML

こんな感じの階層を用意しておきます。上の ImageView には leftPage という id を設定しておきます。

  • AnchorPane
    • SplitPane(Horizontal Flow)
      • AnchorPane
        • ImageView -> leftPage
      • AnchorPane
        • ImageView

回転軸・座標の定義

コントローラーです。まずは左ページに回転の回転軸・座標を設定します。

    @FXML
    private ImageView leftPage;
    private final DoubleProperty leftPageAngle = new SimpleDoubleProperty();

    @Override
    public void initialize(URL url, ResourceBundle rb) {

        leftPage.setFitHeight(PAGE_HEIGHT);
        leftPage.setFitWidth(PAGE_WIDTH);
        leftPage.setImage(getLeftPageImage());

        // Y 軸中心に回転
        final Rotate leftPageRotate = RotateBuilder
            .create()
            .angle(0)
            .pivotX(leftPage.getFitWidth())
            .pivotY(0)
            .pivotZ(0)
            .axis(new Point3D(0, 1, 0))
            .build();
        leftPageRotate.angleProperty().bind(leftPageAngle);
        leftPage.getTransforms().add(leftPageRotate);

        // めくるときに立体的に見えるように影をつける(いまいち)
        DropShadow leftPageDropShadow = new DropShadow();
        leftPageDropShadow.setOffsetX(-40.0f);
        leftPageDropShadow.setColor(Color.GRAY);
        leftPage.setEffect(leftPageDropShadow);
    }

立体的に回転する Rotate を生成して左ページの ImageView の動きに登録しています。 Y 軸を中心に回転させるには Point3D を指定するのがポイントです。 Point3Dコンストラクタ引数にはそれぞれ X, Y, Z 方向の回転軸を指定します*1。全てに 1 を指定するとねじれるように回転します。また、実際に回転させる時のために angleProperty をメンバー変数にバインドしています。

.pivotX で指定した X 座標、すなわち左ページの右端が回転の中心座標になります。これで、 Y 軸を中心に回転する動きが設定できました。ついでに立体的に見えるように DropShadow を設定していますが、わりといまいちなのでここは改善の余地がありそうです。

なお Rotateコンストラクタでも作れますが、パラメーターが多くてなにがなんだかわからないので、ここではビルダーを使っています。後述の Timeline をはじめ、ビルダーパターンで構築できるようにビルダーが用意されている場合が結構あるので必要に応じて活用すると良さそうです。

回転速度・角度の定義

続いて回転速度・回転角度の設定を Timeline を使って実装します。 Timeline についてはさくらばさんの Java技術最前線 - JavaFX 2ではじめる、GUI開発 第13回 タイムラインを使ったアニメーション:ITpro が詳しいので、こちらを参考に。

    private final Timeline openLeftPageAnimation = TimelineBuilder.create()
        .keyFrames(
            new KeyFrame(
                Duration.ZERO,
                new KeyValue(leftPageAngle, 0.0)),
            new KeyFrame(
                Duration.millis(200),
                new KeyValue(leftPageAngle, 90.0)))
        .onFinished(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent t) {
                // ここで右ページめくりのアニメーションを開始
            }
        })
        .build();

繰り返し使うために Timeline をメンバー変数として定義しています。回転の角度の指定には、先ほどメンバー変数にバインドしておいた RotateangleProperty()を使っています。これで Y 軸を中心に 0 度から 90 度回転します。この角度を逆にしておけば、右からページをめくられた時の動きになります。ここでは省略していますが、 onFinished イベントで右ページのアニメーションを発火して、左ページ -> 右ページのアニメーションを連続させています。

ページめくり起動

なんでもいいのですが、ここではマウスクリックイベントでアニメーションを起動します。

    @FXML
    protected void handleLeftPageMouseClicked(MouseEvent event) {
        openLeftPageAnimation.play();
    }

左ページめくりの実装は以上です。右ページのアニメーションもここまでの内容で実装できます。実際に冒頭の動画の動きを実現するにはページの差し替えなどを実装する必要がありますが、汚い長いので省略しますw

ページめくりといえば、 JavaOne Tokyo 2012 で JavaFX を使った凄いページめくりアニメーションのデモがあったようなのですが、あいにく自分は参加しておらず、また現在現物も見つからなかったので一体どういうものだったのか気になってます。どこかで入手できたりするんでしょうか。

JavaFX Advent Calendar 2013 、明日は Yucchi_jp さんです。よろしくお願いします!

*1:このへん自信ない

2013-10-17

JavaFX で Lightbox 的なもの

JavaFX で Lightbox 的なものを出したかったんです。 Lightbox というと web でよく見るアレですね、背景暗くなって画像表示するアレ。出来上がってから Lightbox って名前を思い出したので、最初に "JavaFX Lightbox" とかで検索しとけばもっといろいろやり方あったんじゃないかと今更アレですが、まぁまがりなりにも出来たので一応まとめときます。

完成品

まずは出来上がりから。親画面のボタンを押すと・・・

f:id:hagi44:20131017220929p:image

こんな感じに Lightbox 的なものが出ます。

f:id:hagi44:20131017220930p:image

画像じゃないんかい!と言われそうですが、単にサンプル作るのがめんどくさかっただけです・・。モーダルダイアログ代わりに使うとかでもいいかもですね。

以下、実装です。

親画面コントローラー( MainWindowController )

普通に FXML とコントローラーを作って、ボタン配置してハンドラーを書きます。 このへんは NetBeans でプロジェクト作った時に自動生成されるやつをいじる感じでおk。

	@FXML
	private Pane rootPane; // FXML のルート要素

	@FXML
	private void handleButtonAction(ActionEvent event) {
		try {
			FXMLLoader loader = new FXMLLoader(getClass().getResource("Ligntbox.fxml"));
			loader.load();
			LightboxController controller = loader.getController();
			controller.showOn(rootPane);
		} catch (IOException ex) {
			// なんか適当にエラー処理でも
			Logger.getLogger(MainWindowController.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

ハンドラー内で FXMLLoader から Lightbox 画面のコントローラーを取得してごにょごにょしています。

Lightbox 画面コントローラー( LightboxController )

Lightbox 的なものを表示するコントローラーと FXML を用意します。親画面のコントローラーのハンドラーで呼ばれている処理は以下のとおりです。

	@FXML
	private Pane rootPane; // FXML のルート要素
	private Pane parent; // 親のルート要素

	@FXML
	private void handleCloseButton(ActionEvent event) {
		parent.getChildren().remove(rootPane);
	}

	void showOn(Pane parent) {
		this.parent = parent;
		// 親のルートとサイズを合わせておく
		rootPane.prefHeightProperty().bind(parent.heightProperty());
		rootPane.prefWidthProperty().bind(parent.widthProperty());

		parent.getChildren().add(rootPane);
	}

親から渡された要素に自身のルート要素を add してる感じですね。これはさくらばさんの JavaFX でプレゼンツール - JavaFX in the Box を参考にさせていただきました。親の Pane をフィールドで保持しているのは閉じるボタンを押された時にリムーブするためです。

背景の薄暗いのは css で。薄暗い AnchorPane の上に白い AnchorPane を載せています。

.lightboxBackground {
	-fx-background-color: rgba(0,0,0,0.5);
}
.ligntboxForeground {
	-fx-background-color: #ffffff;
}

とまぁこんな具合です。もっといい方法があるかもしれんけどまぁいいや・・。

画像を表示する場合

本来の Lightbox のように画像を表示する場合は ImaveView とか使えばいいと思います。しかし ImageView はウインドウをリサイズした場合の画像のリサイズがどうもうまくないです。 JavaFX ImageView resize in StackPane - Stack Overflow からリンクされてる JIRA(要ログイン)に添付されている ImageViewPane なるものを使うといい感じにできます。

2013-08-21

Gradle と JavaFX について JJUG ナイトセミナーで LT してきた

先日 JJUG のナイトセミナーで Gradle と JavaFX について内容のうすい LT してきました。

スライドはこちら。 javafx-gradle の導入的な内容です。

javafx-gradle と NetBeans についてはちょっと古いですが NetBeans + Gradle で JavaFX 開発を始めてみる - hd 4.0 にまとめてますのでよろしければどうぞ。

Mac の LibreOffice でスライド作ったんですが、 PDF エクスポートしようとすると落ちるので Windows に持って行ってエクスポートしました。お陰でフォントが変・・。

初 LT で人前でしゃべるのも得意ではないのでどうかなーと思ったんですが、案の定変に焦るわ時間配分考えられないわ*1で制限時間五分のところ一分も余ってしまい、ひどい有様でした\(^o^)/

あとで動画見返したら早口すぎて何言ってるかわからないし。ビール一人一本のところ二本も飲んだのがまずかったか、いやそうじゃない。ごめんなさい。

そんな残念な状態でしたが、久々に懇親会にも参加して、色んな方とお話できて楽しかったです。なぜかエンドレスでハイボールを頼んでいたので調子乗って飲み過ぎてふらふらしてましたがw

当日の全体的な内容については @kikutaro_ さんが JJUG ナイト・セミナー「ビール片手にLT & 納涼会」のまとめ! #jjug - Challenge Java EE ! でまとめていただいているので、発表の補足でも。

NetBeans7.4 beta の JavaFX Maven サポート

スライドでも書いたとおり、 NetBeans7.4 beta では Maven プロジェクトに JavaFX プロジェクト用のテンプレートが追加されています。これを選択すると JavaFX プロジェクトがビルド可能な pom.xml つきのプロジェクトが生成されます。 FXML とかコントローラーも最初から入ってたりしていたれりつくせり。

ここで生成される pom.xml は特に NetBeans に依存しているわけではなさそうなので、普通にコマンドラインからビルド・実行ができる他、 7.3.1 に持って行ってもビルド・実行することができます。 7.4 beta はまだまだ動作が怪しいようなので、プロジェクトだけ 7.4 で作って 7.3.1 で開けば普通に開発できるんじゃね?と思いましたが、新規ファイル作成で JavaFX の項目が出てこない。 javafx-gradle 使った場合と同じですね。 JavaFX プロジェクトでは新規作成で FXML を選ぶとついでにコントローラーも作れたりして便利なんですが、そもそも FXML が選択肢に出てこないのです。

javafx-gradle では仕方なく普通の JavaFX プロジェクトで作成したファイルをコピペしてきたりしてますが、これは 7.4 で是非なんとかしてもらいたいものですね。なお、 7.4 beta では Gradle サポートが対応していないらしくインストールできなかったので、 javafx-gradle の場合にどうなるかは確認できてません。もし Maven サポートがいい感じなら Maven でもいいかなという気はしますが。

今後に期待ですねー。

*1:折角タイマーを用意していただいていたのに、そっちに目をやる余裕すらなかった

2013-06-21

FXML から FXML を参照する

JavaFX で一つのウィンドウに同じ要素(コンポーネント?)をいくつか貼り付けたいなーとかいう場合。例えば二画面ファイラーの左右ファイル窓とかですね。これらは左右で全く構造が同じです。または複数の画面から同じ要素を参照したい場合。いくら Scene Builder でぽとぺた便利だからって全く同じものを二つ作るのはメンテナンス的にも精神衛生的にもよろしくないですね。

というわけで共通要素だけ別の FXML に抜き出して、親 FXML から参照するとかできないのかなーと思って調べると Nested Controllers なんてのが見つかりました。

fx:include なるものを使って FXML から別の FXML を読み込む感じのようです*1。親コントローラーから @FXML を使って子のコントローラーや子の要素自身をインジェクションさせることもできます。

上記ページでは親コントローラーに子要素を Window としてインジェクションするようになってますが、ここは子の FXML のルート要素の型に合わせる感じになるようです。また、親コントローラーに子コントローラーをインジェクションする場合、 fx:include で指定した id + Controller というフィールド名にする必要があるようです。ここが違ってると子コントローラーのフィールドが null になります。

親の FXML がこんな感じ(だいぶはしょってます)だったとすると、

<AnchorPane id="AnchorPane" xmlns:fx="http://javafx.com/fxml" fx:controller="sample.MainWindowController">
    <children>
        <fx:include fx:id="child" source="childView.fxml"/>
    </children>
</AnchorPane>

親のコントローラーはこんな感じになります。

public class MainWindowController implements Initializable {

    @FXML
    private AnchorPane child;
    @FXML
    private ChildController childController;
    // 子要素の id 名 + Controller にする必要があるので、これはだめ
    // private ChildController childXXXController;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // 子コントローラーを参照できる
        childController.doSomething();
    }
}

子の FXML はこんな感じ。

<AnchorPane xmlns:fx="http://javafx.com/fxml" fx:controller="sample.ChildController" >
    <children>
        (略)
    </children>
</AnchorPane>

子のコントローラーは普通に作ればいいです。子のコントローラーが Initializable を実装している場合、親のコントローラーが初期化されるタイミングで子のコントローラーの initialize も呼ばれるようです*2

ていうか JavaFX-Gradle 使って JavaFX アプリケーション作ると NetBeans の新規ファイル作成ウィザードから JavaFX の項目消えるのはなんなんだ・・・。

*1:これは Scene Builder からはできないのかな?

*2:親子両方 Initializable の場合は子 -> 親の順で呼ばれるっぽい。考えてみれば当たり前ですが。