プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

JavaFX練習3:アニメーションとか

 前回の続きで今日はアニメーションについて練習しました。あと、SceneはSwingのContentPane相当だということを理解しました。
 しかし、あれだね。JavaFXで3Dが使えるようになると言うことで、今のうちに覚えておこうとやっているんだけど、やっぱりJavaFXって好かんわ。もちろん、Swingよりずっと勝ってるところがあるのはわかるんだよ。でもね、気持ち悪いんだ。
 一番気持ち悪いのはNodeのサイズをいったい誰がどうやって計算してるのかさっぱりわからんこと。prefWidthやらmaxWidthやらminWidthやらはあっても、全部値が-1だったりして、おま、どうやって大きさ決めてんねん。意味わからんわ。だから、初心者の私には挙動がさっぱりわからん。どこをどう設定したら自分の目的とする大きさに設定できるのかがわからん。あと、レンダリングがいったい誰がいつどうやってやってんのかさっぱり見えないこと。設定、設定、で動くのは確かに便利かもしれんが、SwingでpaintComponentやGraphicsとかで直接指示出したり、画像処理で直にピクセル弄ってる人間からしたら気持ち悪くてしょうが無い。もうすでに老害というやつになってるんでしょうか、私。


アニメーション

 JavaFXではアニメーションはAnimationクラスのサブクラス達を使って行うみたいです。挙動はどうあれ、アニメーションの動きの基本は、

こんな感じのようです。このアニメーションはJavaFX Application Threadという名前のスレッドで動いているみたいです。JavaFX Application Threadはどうやら、SwingでいうところのEDTみたいなもののようです。ということは、うっかりアニメーション処理で重たい処理を挟むとJavaFXが止まると言うことなんでしょうかね。
 で、Animationクラスは大きく分けて、TimelineとTransitionの二つで、Timelineはいろんな値を扱ったアニメーションするために、Transitionは対象ノードのプロパティの値を変化させるアニメーションをするために利用するみたいです。

 TimelineはKeyFrameというクラスを使って複数の動作をするアニメーションを定義できるようです。Timelineと言う名前から、私は最初このクラスの挙動は、KeyFrame1を実行後、KeyFrame2を実行して、その後KeyFrame3を………というものだと思っていたのですが、全部いっぺんに動作し始めるみたいでした。

 この図で言うと、上の挙動をするとおもっていたけど、下の方だったということです。

 Transitionは、FadeTransition, FillTransition,PathTransition, RotateTransition, ScaleTransition, StrokeTransition, TranslateTransitionがあり、それぞれ透明度の変化だったり、移動だったりのアニメーションを行ってくれます。また、これらのアニメーションを同時に全て実行するアニメーションをつくるParallelTransition、アニメーションが終わると次のアニメーションを実行するSequentialTransitionがある。


PauseTransitionの価値がよくわかんないっす。setDelayじゃだめなんすか?


スライドイン、スライドアウトするアニメーションを作ってみる

 前回のやつをアニメーションするようにします。アニメーションは

  1. スライドインしながらフェードイン もしくは スライドアウトしながらフェードアウト
  2. 矢印の向き回転

 という動きをするようにします。

 まずは、スライドの動きを作りたいのですが、JavaFXのアニメーションをさせるには何はともあれWritablePropertyが何か必要なんですが、widthPropertyはReadOnlyだし、maxwidthとかの値はそもそもpropertyがない。

 どうすればいいのか、よくわからなかったので、正攻法かどうかわかりませんが、自分で以下のようなWritablePropertyを定義してみました。

    //AnchorPane直下のVBoxもControllerで受け取れるように変更しました。
    public VBox contents_info;
    //サイズを変えるために、間に噛ませたプロパティ
    private DoubleProperty infoboxWidth=new SimpleDoubleProperty(1){
        public void set(double d) {
            super.set(d);
            //dによってmaxWidthを変化させる
            if(d==1d){
                infobox.setMaxWidth(-1);
                return;
            }
            double contents_info_width = contents_info.getWidth();
            double boxwidth = contents_info_width*d;
            infobox.setMaxWidth(boxwidth);
        }
    };

 で、このプロパティの値を0〜1まで変化させることで、スライドのアニメーションを作ってみました。ノードのサイズの変更は、resizeRelocateを使ったら出来るかと思ったのですが、スライドさせることが出来ませんでした。
 以下はアニメーション定義の全容です。全てのソースコードはGitHubにあげておきました。
 もっとうまいやり方があるのかもしれませんが、とりあえず、今回はゴリゴリアニメーションの定義を書いてみました。一応、目的とする動作をさせることが出来ました。



    private SequentialTransition openAnimation,closeAnimation;

    public void slideAction(ActionEvent event){
        if(isOpen)closeAnimation.play();
        else openAnimation.play();
        isOpen = !isOpen;
    }

    @Override
    public void initialize(URL url, ResourceBundle resource) {
        //アニメーション設定

        //スライドインさせるアニメーション
        Timeline slidein = new Timeline(
                new KeyFrame(new Duration(0), new EventHandler<ActionEvent>(){
                    @Override
                    public void handle(ActionEvent e) {
                        if(closeAnimation.getStatus()==Status.RUNNING)
                            closeAnimation.stop();//ついでなので、クローズアニメーションをストップさせてみた
                    }

                }),
                new KeyFrame(new Duration(300),new KeyValue(infoboxWidth,1))
                );
        //フェードインさせるアニメーション
        //Timelineに追加してもいい気がしたけど、
        //練習のためにFadeTransitionを利用
        FadeTransition fadein = new FadeTransition(new Duration(300),infobox);
        fadein.setFromValue(0);
        fadein.setToValue(1);
        fadein.setDelay(new Duration(100));//ちょっと開始を遅らせる。

        //スライドとフェードを結合
        ParallelTransition open = new ParallelTransition(slidein,fadein);

        //スライドインが終わった後にボタンの向きを回転させる
        RotateTransition openrotate = new RotateTransition(new Duration(300),sbutton);
        openrotate.setToAngle(-90);
        openrotate.setFromAngle(90);
        openrotate.setDelay(new Duration(100));


        //openと回転を順次実行するアニメーション
        openAnimation = new SequentialTransition(open,openrotate);

        //スライドアウト
        Timeline slideout = new Timeline(
                new KeyFrame(new Duration(0), new EventHandler<ActionEvent>(){

                    @Override
                    public void handle(ActionEvent e) {
                        if(openAnimation.getStatus()==Status.RUNNING)
                            openAnimation.stop();
                    }

                }),
                new KeyFrame(new Duration(300),new KeyValue(infoboxWidth, 0))
                );
        //フェードアウト
        FadeTransition fadeout= new FadeTransition(new Duration(200),infobox);
        fadeout.setFromValue(1);
        fadeout.setToValue(0);

        //結合
        ParallelTransition close = new ParallelTransition(slideout,fadeout);

        //回転
        RotateTransition closerotate = new RotateTransition(new Duration(300),sbutton);
        closerotate.setToAngle(90);
        closerotate.setFromAngle(-90);
        closerotate.setDelay(new Duration(100));
        //結合
        closeAnimation = new SequentialTransition(close,closerotate);