ループアニメーション

このエントリーはJavaFX Advent Calendarの25日目です。

qiita.com

12月15日に開催されたJJUG CCCでStream APIについて登壇しました。その内容はさておき、そのプレゼン資料で使用したアニメーションについて紹介しておきます。

知っている人には当たり前化もしれないですけど。

このプレゼンではStreamということで、流れるものをモチーフにした絵や写真、音楽などを使用しました。タイトルやセクションのタイトルで使用したアニメーションもそう。ベルトコンベアで文字が流れてくるという感じにしています。

このスクリーンショットは文字が切れてしまっているのではなくて、流れている途中なのです。

このアニメーションなんですが、実をいうととても簡単に作ってます。

原理的には短いアニメーションをループさせているだけ。

まず、簡単なサンプルで示しましょう。

package net.javainthebox.loopanim;

import java.util.stream.IntStream;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class LoopAnimation extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Pane root = new Pane();
        init(root);

        Scene scene = new Scene(root, 400, 100);
        stage.setScene(scene);
        stage.show();
    }

    void init(Pane pane) {
        Rectangle rectangle = new Rectangle(0.0, 50.0, 40.0, 10.0);
        rectangle.setFill(Color.WHITE);
        rectangle.setStroke(Color.BLACK);
        pane.getChildren().add(rectangle);

        startAnimation(rectangle);
    }

    void startAnimation(Node node) {
        TranslateTransition trans = new TranslateTransition(Duration.millis(1_000), node);
        trans.setToX(40.0);
        trans.setInterpolator(Interpolator.LINEAR);
        trans.setCycleCount(Animation.INDEFINITE);
        trans.play();
    }

    public static void main(String... args) {
        launch(args);
    }
}

このサンプルを実行すると、長方形が移動を繰り返すというアニメーションを実行します。

この長方形は40ピクセルだけ横方向に移動しています。肝となるのは長方形の幅も40ピクセルだということ。

長方形を描画しているのが、initメソッドです。長方形を生成している部分を抜き出しました。

        Rectangle rectangle = new Rectangle(0.0, 50.0, 40.0, 10.0);

第1, 2引数が長方形の左上の座標。第3引数が幅で、第4引数が高さです。

これに対し、アニメーションしているのが、startAnimationメソッドです。抜粋したのが、以下。

    void startAnimation(Node node) {
        TranslateTransition trans = new TranslateTransition(Duration.millis(1_000), node);
        trans.setToX(40.0);
        trans.setInterpolator(Interpolator.LINEAR);
        trans.setCycleCount(Animation.INDEFINITE);
        trans.play();
    }

移動のアニメーションを行うのが、TranslateTransitionクラスです。コンストラクタの第1引数がアニメーションの長さ、第2引数がアニメーションを行うターゲットです。

第1引数で1秒を指定しているので、1秒のサイクルで繰り返しを行っています。

そのあとの、setToXメソッドが移動した後のX座標を示しています。0から移動しているので、40ピクセルでちょうど長方形の幅と一緒になっています。

次のsetInterpolatorメソッドははじめゆっくり、中は急いで、最後はまたゆっくりなどのように、アニメーションの動きの見た目のスムーズさなどを指定します。ただ、ここでははじめゆっくりとかやっていると、ぎこちなくなるので、常に同じ速さでアニメーションするLINEARを指定しています。

setCycleCountメソッドはアニメーションの繰り返し回数を指定しますが、ここでは無限ループにしています。

最後にplayメソッドでアニメーションの開始です。

これで、長方形が繰り返し移動するアニメーションができます。

長方形1つだけなら意味ないと思いますよね。そうです、この長方形を並べてあげれば、ベルトコンベアのベルト的なものになるわけです。

移動する時に左側が欠けてしまわないように、画面の外側の部分から用意しておきます。ということで、initメソッドを以下のように変更しました。

    void init(Pane pane) {
        Group group = new Group();
        IntStream.range(0, 11)
                .forEach(i -> {
                    Rectangle rectangle = new Rectangle(40.0*i - 40.0, 50.0, 40.0, 10.0);
                    rectangle.setFill(Color.WHITE);
                    rectangle.setStroke(Color.BLACK);
                    group.getChildren().add(rectangle);
                });
        pane.getChildren().add(group);
        
        startAnimation(group);
    }

複数の長方形をGroupオブジェクトでまとめています。

このGroupオブジェクトをアニメーションすれば、あら不思議、永遠にベルトコンベアが動いているように見えます。

http://f.hatena.ne.jp/skrb/20181225223729

スクリーンショットなので、動きがよく分からないとは思いますが、ぜひサンプルを試してみてください!

同じ原理を使えば、歯車とか、エスカレータなどのアニメーションもできるはず。実際にCCCのプレゼンでは歯車も一緒に回転させています。

ということで、ループアニメーションの紹介でした。