Hatena::ブログ(Diary)

hd 4.0 RSSフィード

2016-12-13

javafx-maven-plugin でネイティブ用アイコンを設定する

この記事は JavaFX Advent Calendar 2016 - Qiita の 13 日目です。

昨日は @boochnich さんの JavaFXのCanvasで拡大縮小と平行移動を行う(リベンジ編) - Qiita でした。明日は @y_q1m さんです。

はじめに

JavaFX の最大の魅力の一つはなんといってもクロスプラットフォームGUI アプリを作れることです。 jar 形式でなく、各プラットフォーム向けの実行ファイル形式にビルドする方法も提供されており、 Javaインストールされていない環境でも動作するアプリを作成することができます。

Java 開発といえば Maven や Gradle がデファクトになっていますが、 javafx-maven-plugin を使うと、 JavaFX 開発で Maven を使いつつプラットフォームネイティブJavaFX アプリビルドすることができます。 NetBeansJavaFX + Maven アプリケーションを作ると javapackager を直で使うような構成の pom が出来上がりますが、 javafx-maven-plugin の方がビルドが速いしパッケージングも楽なので、自分は専らこれを使っています。

javafx-maven-plugin を導入する際は、以下のページからぽちぽちオプションを選んでいくと pom 設定の雛形が自動生成されるので便利です。

前置きが長くなりましたが、せっかくネイティブアプリができるのなら、アプリケーションアイコンデフォルトのじゃばじゃばしいやつじゃなくて、独自のものを設定したくなるものです。 Oracle の公式とかだと javapackager を ant から呼ぶみたいなサンプルしかなくて、今更 build.xml もちょっと・・・ということで、 Maven でどうやってアイコンを設定するのか調べてみました。

環境

環境は以下のとおりです。

結論

まず結論から。最低限以下のファイルを用意して mvn jfx:native すればいいです。 Linux は環境がないので調べてませんが、後述のように調べれば同様にできると思います。

アプリ名は pom の build -> finalName です。詳細は以下に述べます。

アイコンファイルをどこに置くか

とりあえず Oracle の公式は以下のような感じです。

To get more insight into what resources are being used, enable verbose mode by adding the verbose="true" attribute to <fx:deploy>, or pass the -v option to the javapackager -deploy command.

なんかよくわかりませんが、ビルド時に verbose="true" を指定すれば、どういうリソースを使うのかわかるよ!って感じでしょうか。しかしここの例では ant なので、 Maven における verbose オプションの指定方法がわかりません。 mvn コマンドの -X オプションを試してみたが、これではないようです。

ここで前述の javafx-maven-plugin の公式サイトを見てみましょう。

lag to turn on verbose logging. Set this to "true", if you are having problems and want more detailed information.

そのものずばり verbose オプションを指定するスイッチがありますね。これをオンにすると pom.xml に以下のように追加されます。

	<groupId>com.zenjava</groupId>
	<artifactId>javafx-maven-plugin</artifactId>
	<version>8.6.0</version>
	<configuration>
		<mainClass></mainClass>
		<verbose>true</verbose>
	</configuration>

この状態で mvn jfx:native を叩くと以下のような出力が得られます(抜粋)。 Oracle 公式にあったような感じですね。

  Using default package resource [icon]  (add package/macosx/アプリ名.icns to the class path to customize)
(略)
  Using default package resource [dmg background]  (add package/macosx/アプリ名-background.png to the class path to customize)
  Using default package resource [volume icon]  (add package/macosx/アプリ名-volume.icns to the class path to customize)

上記は Mac で試したものですが、実行環境の OS により少しパスが異なります。

どうやら Mac の場合は package/macosx/アプリ名.icns というやつを用意してクラスパスに追加すればよさそうです。他にも アプリ名-background.pngアプリ名-volume.icns とかいうのもありますね。クラスパスということですがどこに置くのがよいのか、ここで javafx-maven-plugin の公式サイトをもう一度よく読みます。

Default: ${project.basedir}/src/main/deploy

(略)

The most common usage for this is to provide platform specific icons for native bundles. In this case you need to follow the convention of the JavaFX packaging tools to ensure your icons get picked up.

なるほど、 ${project.basedir}/src/main/deploy/package/macosx を用意すれば良いっぽいですね。

アイコンファイルの用意

というわけでアイコンファイルを用意しましょう。 icns という見慣れない拡張子当社比)が出てきましたが、これはなんでしょうか。

Mac 用のアイコンファイルのようですね。ターミナルから作れるようなので作ってみましょう。元ネタの png はなんとかして用意する*1として、各サイズは Macプレビューアプリからリサイズすることで作れます。

icons_256x256.png とかいろんなサイズの画像を作り、 アプリ名.iconset というフォルダに突っ込みます。しかるのちにターミナルで以下のコマンドを叩くと、 アプリ名.icns が出来上がります。

$ iconutil -c icns アプリ名.iconset

今回はめんどくさいので 256x256 だけ用意しましたが特に問題なく作成できました。ファイル名で表されたサイズと実際のファイルサイズが異なっているとうまく icns ファイルが作成されないので気をつけましょう。

出来上がったアイコンファイルを ${project.basedir}/src/main/deploy/package/macosx に配置し、改めて mvn jfx:native を叩きます。

  Using custom package resource [icon]  (loaded from package/macosx/アプリ名.icns)
(略)
  Using default package resource [dmg background]  (add package/macosx/アプリ名-background.png to the class path to customize)
  Using default package resource [volume icon]  (add package/macosx/アプリ名-volume.icns to the class path to customize)

作成したアイコンファイルが読まれているのがわかります。これでめでたくアイコンが設定できました。

f:id:hagi44:20161208001020p:image

インストールすると、ちゃんと Finder や Dock に指定したアイコンが表示されます。

アプリ名-volume.icns はインストーラーがマウントされたときにデスクトップに出現するボリュームアイコン用のようです。このファイルを指定するとデスクトップに以下のアイコンが現れます。今回は同じアイコンファイルを使っています。

f:id:hagi44:20161208001022p:image

指定しない場合、デフォルトのジャバジャバしいやつです。

f:id:hagi44:20161208001021p:image

アプリ名-background.pngインストーラーの背景画像のようです。このファイルを指定すると以下のようになります。

f:id:hagi44:20161211120105p:image

なんかうっかりひどい色になってしまいました。すいません。とりあえず画像サイズは 512x256 にしてみたのですが、微妙に合ってないですね。

ウィンドウアイコンの設定

アプリのタイトルバーに表示するアイコンは以下のようにして設定できます。これ自体はネイティブアプリとは関係ないです。

  1. アイコンファイルを src/main/resources/images/icon.png あたりに配置する
    • Java コードから参照できる場所であればどこでも良いです
  2. JavaFXインクラスの start メソッドアイコンパスを指定する

アイコンパスの指定は以下のようなコードになります。

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("/fxml/Scene.fxml"));

        Scene scene = new Scene(root);
        scene.getStylesheets().add("/styles/Styles.css");

        // ↓この 1 行
        stage.getIcons().add(new Image(getClass().getResourceAsStream("/images/icon.png")));

        stage.setTitle("JavaFX Icon Sample");
        stage.setScene(scene);
        stage.show();
    }

これでこんな感じになります。もうちょいはっきりしたアイコンにしないと小さくてわかりづらいですね。

f:id:hagi44:20161211114635p:image

Mac の場合は未設定だとアイコン自体が表示されないのでなくても特に気になりませんが、 Windows だとデフォルトのウィンドウアイコンになってしまい、残念な感じになるのでこちらも設定しておいた方が良いでしょう。

Windows の場合

同様に Windowsmvn jfx:native を実行すると以下のような出力が得られます(抜粋)。

  Using default package resource [application icon]  (add package/windows/アプリ名.ico to the class path to customize)

Windows だと設定可能なアイコンが 1 種類しかないようですね。インストーラーを作成できるようにしてあるとまた違うのかもしれませんが、そのへんは未調査です。メッセージに従い、 src/main/deploy/package/windows/アプリ名.ico を用意して再度ビルドしてみると、 target/jfx/native/アプリ 以下に実行ファイル類が一式作成され、アイコンもちゃんと設定されていることがわかります。

f:id:hagi44:20161212184355j:image

以上です。

*1:とりあえずなんでもいいのであれば Placehold.jp|ダミー画像生成 モック用画像作成 でダミー画像を作るのもありかと思います。 advanced オプションCSS も使えるので、角丸やらグラデーションやら使えばそれなりにそれっぽくもなります。

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アプリケーション でした。

JavaFX で、本のページをめくるようなアニメーションが欲しいなーと思ったので作ってみました。めくりということで 3 日目の記事 caffeineswitch - TOP とややかぶり気味になってしまいましたがご容赦を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:折角タイマーを用意していただいていたのに、そっちに目をやる余裕すらなかった