タツノオトシゴのブログ

主にJavaに関するものです。

JavaFXのスプラッシュ画像・プレローダの表示

アプリケーションを起動する際に待ち時間がある場合に表示するスプラッシュ画像やローディング中などの画面を表示する方法を説明します。

  • JavaFXではPreloaderという専用機能があり、FXMLなどで自分で自由にカスタマイズができます。
  • スプラッシュ画像は、java標準の起動オプション「-splash:<ファイル名>」を利用します。

JavaFXでプレローダの表示

はじめに

javafx.application.Preloader」は、「javafx.application.Application」を継承しています。
そのため、自分のApplication本体とプレローダ用のアプリケーションを用意します。

JavaFXのアプリケーションは、起動フローとして次のようになっており、Preloaderクラスに現在どの段階か通知して連携します。

  1. JavaVMの起動。クラスファイルなどをメモリ上にロードします。
    • スプラッシュの表示。
    • JNLPの場合、Java標準のものが表示されます。カスタマイズもできます。
  2. JARファイルなどのダウンロード。(JNLP専用)
    • 「StateChangeNotification.Type.BEFORE_LOAD」で、Preloaderに通知されます。
  3. Application#init()メソッドの実行。
    • 「StateChangeNotification.Type.BEFORE_INIT」で、Preloaderに通知されます。
  4. Appliction#start()メソッドの実行。
    • 「StateChangeNotification.Type.BEFORE_START」で、Preloaderに通知されます。

プレローダは、本体のApplicationとは別なApplicationなので、「MANIFEST.MF」ファイルの項目「JavaFX-Preloader-Class」で指定する必要があります。
そのため、JavaFX用のAntタスクを利用してパッケージングする必要があります。

NetBeansを使うと画面で簡単にできますが、ここではAntタスクを使った方法を説明します。
JavaFXの公式サイトでは、Preloader用とプログラム本体が別々なjarになっていますが、ここでは同じjarにあることを前提とします。

Preloaderの作成

Preloaderクラスを作成します。ここでは、最もシンプル形式にします。
凝ったものにしたい場合などは、公式サイトにサンプルが載っています。

import javafx.application.Preloader;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

/**
 * もっともシンプルなプレローダ
 */
public class FirstPreloader extends Preloader {
    
    ProgressBar bar;
    Stage stage;
 
    private Scene createPreloaderScene() {
        bar = new ProgressBar();
        BorderPane p = new BorderPane();
        p.setCenter(bar);
        return new Scene(p, 300, 150);        
    }
    
    @Override
    public void start(Stage stage) throws Exception {
        this.stage = stage;
        stage.setScene(createPreloaderScene());        
        stage.show();
    }
    
    @Override
    public void handleProgressNotification(ProgressNotification pn) {
        bar.setProgress(pn.getProgress());
    }
 
    @Override
    public void handleStateChangeNotification(StateChangeNotification evt) {
        if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
            // Application#start()の実行まえにプレローダを閉じる
            stage.hide();
        }
    }    
}
アプリケーション本体

特に凝ったことをしなければ、initメソッドやstartメソッドが呼ばれるときに自動的にPreloaderに通知されるため、
何もする必要はありません。

JavaFXのAntタスクの設定

プレローダを指定する際には、要素の属性「preloaderClass」で指定します。
ともは共通なので記述方法は同じです。

<?xml version="1.0" encoding="UTF-8"?>
<!-- ===============================================
JavaFXのパッケージングをするAntXMLファイル

====================================================-->
<project name="sample-app" basedir="." default="jfx-package" xmlns:fx="javafx:com.sun.javafx.tools.ant">
    
    <!-- JavaFXのアプリケーション用のAnt用のjarファイルのパスの取得 -->
    <property environment="env" />
    <property name="javafx.tools.ant.jar" value="${env.JAVA_HOME}/lib/ant-javafx.jar" />

    <!-- コンパイル済みのclassファイルの格納場所:mavenの形式 -->
    <property name="dest" location="target/classes" />
    
    <!-- 実行可能な Jar ファイルが作成されるフォルダ:mavenの形式 -->
    <property name="jardest" location="target" />

    <!-- アプリケーションの設定 -->
    <property name="app.vendor" value="javafx.sample" />
    
    <!-- 実行可能 Jar ファイルの名前(の一部となる)。mavenから実行する場合pom.xmlから引き継ぐ。 -->
    <property name="app.id" value="sample-app" />
    <property name="app.name" value="sample-app" />
    <property name="app.version" value="2.0" />

    <!-- メインクラス -->
    <property name="app.main-class" value="sample.gui.SampleApplication" />
    
    <!-- ★プレローダクラスのパスの定義★ -->
	<property name="app.preloader-class" value="proj.green.srcgen.gui.FirstPreloader"/>
    
    <!-- JavaFX専用の形式にパッケージングするタスク -->
    <target name="jfx-package" description="packaging JavaFX archtecture.">
        
        <echo>JavaFX用のjarをパッケージングしています。</echo>
        
        <!-- タスク定義 -->
        <taskdef resource="com/sun/javafx/tools/ant/antlib.xml"      
                 uri="javafx:com.sun.javafx.tools.ant"
                 classpath="${javafx.tools.ant.jar}"/>
        
        <!-- JavaFXのアプリケーション定義:★属性preloaderClassでPreloaderを指定する★ -->
        <fx:application id="app-info" 
                  name="${app.name}"
                  mainClass="${app.main-class}"
                  preloaderClass="${app.preloader-class}">
        </fx:application>
        
        <!-- JavaFXのパッケージ作成 -->
        <fx:jar destfile="${jardest}/${app.id}-${app.version}.jar">
            <fx:application refid="app-info"/>
            
            <!-- アプリケーションの起動パラメータなど:オプション -->
            <fx:platform javafx="2.2+">
                <fx:jvmarg value="-Xms64m" />
                <fx:jvmarg value="-Xmx128m" />
            </fx:platform>
            
            <!-- MANIFEST.MFファイルの内容 :オプション-->
            <manifest>
                <attribute name="Implementation-Vendor" value="${app.vendor}"/>
                <attribute name="Implementation-Title" value="${app.name}"/>
                <attribute name="Implementation-Version" value="${app.version}"/>
            </manifest>
            
            <!-- パッケージング対象のファイル -->
            <fileset dir="${dest}"/>
            
            <!-- JavaFX-Class-Pathに追加される値:依存するライブラリを定義 -->
            <fx:resources>
                <fx:fileset dir="target/dependency" includes="*.jar"/>
            </fx:resources>
            
        </fx:jar>
        
    </target>

</project>

あとは、普通にパッケージングすればよいです。

> mvn -Dmaven.test.skip=true compile
> ant -f javafx_build.xml jfx-package

作成したjarファイルをダブルクリックまたは、java -jar で実行するとプレローダを含めたものが起動します。

JavaFXでスプラッシュの表示

下記を参考にしました。

プレローダを利用する場合、JavaFX用にパッケージングする必要があるため、非常に面倒です。
Java標準の「-splash」オプションを使用すると手軽に実現できます。

スプラッシュを利用したJavaFXのApplicationのサンプル

Splash画像上にJava2Dを利用してメッセージなども表示できます。

AWTの「java.awt.SplashScreen」のメソッドで「#getSplashScreen()」でスプラッシュ画像の情報が取得できます。

  1. Application#init()メソッドの中で、SplashScreenのオブジェクトを取得します。
    • Java2Dなどで起動中のメッセージを書くこともできます。
  2. Application#start()メソッドの中で、Stageを表示する直前に、SplashScreen#close()でスラッシュ画像を非表示にします。
    • close()メソッドを呼ぶと、スプラッシュ画像のオブジェクトが廃棄されます。
package proj.green.srcgen.gui;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.SplashScreen;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

/**
 * スプラッシュ画像のサンプル
 */
public class SampleApplication extends Application {
    
    /**
     * スプラッシュ
     * <p>起動時に「-splash:<画像ファイル>」を指定する。
     */
    private SplashScreen splashScreen;
    
    /**
     * Springの初期化
     */
    @Override
    public void init() throws Exception {
        
        // スプラッシュの更新
        splashScreen = SplashScreen.getSplashScreen();
        updateSplashMessage("... 初期化中です。");
        
        //TODO: Springのコンテナなどなどの初期化
    }
    
    /**
     * スプラッシュのメッセージを更新する。
     * <p>Java2Dを使用する。
     * @param message
     */
    private void updateSplashMessage(final String message) {
        if(splashScreen == null) {
            return;
        }
        
        // 文字を表示するボックスの位置
        final int x = 20;
        final int y = splashScreen.getSize().height - 60;
        
        final Graphics2D g = splashScreen.createGraphics();
        
        // 文字を表示する部分を塗りつぶす。
        // 前回の表示文字を消すためにも塗りつぶす。
        g.setColor(Color.WHITE);
        g.fillRect(x, y, splashScreen.getSize().width - x - 20, 30);
        
        // 文字を設定する
        g.setColor(Color.BLACK);
        g.setFont(new Font("SansSerif", Font.PLAIN, 15));
        g.drawString(message, x+10, y+20);
        
        splashScreen.update();
    }
    
    @Override
    public void start(final Stage primaryStage) throws IOException {
        
        updateSplashMessage("... 画面の初期化中です。");
        
        //TODO: ステージの組み立て
        
        // スプラッシュの非表示
        if(splashScreen != null) {
            splashScreen.close();
        }
        
        // ステージの表示をフォーカスを充てる
        primaryStage.show();
        primaryStage.requestFocus();
    }
    
    @Override
    public void stop() throws Exception {
        
        //TODO: Springのコンテナの終了
        
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

スプラッシュ画像の指定

javaの起動オプションに「-splash:<画像ファイルパス>」を指定します。
画像ファイルは、「JPEGPNG、GIF」の3種類に対応しています。

ファイルパスは、システムパスで指定する必要があるので、バッチファイルからアプリケーションを起動する際には、
jarファイルのカレントディレクトリに移動して、相対パスで指定するなどしてください。

○ファイル構成

 ├ sample-app.jar  ## jarファイル本体
 ├ config          
      ├ splash.png ## スプラッシュ画像
@echo off

REM 起動用バッチファイルのサンプル

%~d0
cd %~p0

set MY_CLASSPATH=.;dependency/*;

%JAVA_HOME%\bin\java -cp %MY_CLASSPATH% -splash:config/splash.png sample.SampleApplication
MANIFEST.MFでの指定

【参考】
http://www.javainthebox.net/laboratory/JavaSE6/splash/splash.html

スプラッシュ画像は、起動オプション「-splash」の他に「MANIFEST.MF」に指定することもできます。

○mavenで指定する際には、「maven-jar-plugin」で指定します。

<!-- Splash画像のMANIFESTの指定サンプル -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>
            <manifest>
                <mainClass>sample.SampleApplication</mainClass>
            </manifest>
            <manifestEntries>
                <!-- 任意の項目・値が設定可能 -->
                <Splashscreen-Image>config/splash.png</Splashscreen-Image>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>


○JavaFXのパッケージング時に指定する際には専用のまたは、任意の項目を指定する形式で指定します。

<fx:info title="Sample application">
    <fx:splash href="http://my.site/custom.gif"/> 
</fx:info>