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

この記事は JavaFX Advent Calendar 2013 - Adventar の 9 日目です。昨日は @kokuzawa さんの IntelliJ IDEA 13で作るJavaFXアプリケーション | KATSUMI KOKUZAWA'S BLOG でした。

JavaFX で、本のページをめくるようなアニメーションが欲しいなーと思ったので作ってみました。めくりということで 3 日目の記事 http://caffeineswitch.net/rulog/javafx%E3%81%A7%E3%82%81%E3%81%8F%E3%82%8B%E3%82%A8%E3%83%95%E3%82%A7%E3%82%AF%E3%83%88%EF%BC%81%EF%BC%81/ とややかぶり気味になってしまいましたがご容赦を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 についてはさくらばさんの JavaFX 2ではじめる、GUI開発 第13回 タイムラインを使ったアニメーション | 日経 xTECH(クロステック) が詳しいので、こちらを参考に。

    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:このへん自信ない