bindの謎 ふたたび

JavaFXでグラデーションを使ったアニメーションを作ろうとしてbindの罠にはまった.こんなアニメーションをつくりたくて,LinearGradationのオフセットを制御して実現しようとしたのだが,どうにもうまく行かない.いろいろ調べてちょっと分かったので書いておく.作りたかったアニメーションはこんなやつ.床屋の看板 (^_^).

http://video.google.com/videoplay?docid=7868435584785123309

bindの位置と挙動

Pointという属性xを持つクラスをつくり,属性xに x0をbindすることを考える.

public class Point {
    attribute x: Number;
}
var x0:Number = 10.0;

この場合,次の3通りの書き方が考えられる.

var p0:Point = bind Point{x:      x0 };
var p1:Point = bind Point{x: bind x0 };
var p2:Point =      Point{x: bind x0 };

x0を変更して,それぞれの属性xを参照してみると,どの書き方でもちゃんと更新されていることが確認できる.

x0 = 20.0;

System.out.println(p0.x);
System.out.println(p1.x);
System.out.println(p2.x);

とやると

20.0
20.0
20.0

となる.

なので,一見どの書き方でも挙動は同じであるように思えてしまう.

実は挙動が違う

実際に起こっていることは全然違っていたりする.x0の変更前後でPointオブジェクト本体を書き出してみる.toStringメソッドを定義していないので,オブジェクトのポインタが書き出される.

System.out.println(p0);
System.out.println(p1);
System.out.println(p2);
x0 = 20.0
System.out.println("");
System.out.println(p0);
System.out.println(p1);
System.out.println(p2);

結果は以下のようになる.

mytest.bindTest$Point@215f7107  <---
mytest.bindTest$Point@f593af
mytest.bindTest$Point@7ab2c6a6

mytest.bindTest$Point@6f0ffb38  <---
mytest.bindTest$Point@f593af
mytest.bindTest$Point@7ab2c6a6

p0だけはオブジェクト本体が更新されている.つまり,別のオブジェクトが作られ,古いのは破棄されているのだ.この動作は,オブジェクトがどんどん使い捨てられてしまうので,あまりのぞましくない.しかし,描画オブジェクトのグラデーションや,フォントを変更する際には,オブジェクト本体を更新しなければ,変更が描画に反映されないようなのだ.これが,私がグラデーションをつかったアニメーションではまった原因.

ややこしいのは,RectangleやらLineやらの場合は,オブジェクト本体を更新する必要は無い,ということ.なんか,納得いかないが,javafx.scene.NodeのサブクラスはOKということなのか?

まとめ
var p0:Point = bind Point{x:      x0 };  <- オブジェクト書き変わる
var p1:Point = bind Point{x: bind x0 };  <-           変わらない
var p2:Point =      Point{x: bind x0 };  <-           変わらない

p1 の書き方で変わらないというのがなんか納得いかないのだけど,..

グラデーションによるアニメーション

グラデーションで作ったアニメーションのサンプルを.グラデーションのなかのオフセットを書き換えることでアニメーションさせている.このとき,一フレームごとにLinearGradationのObjectをガンガン使い捨てているわけだ.なんだかなあ.いいんだろうか..

ソースコード
package mytest;

import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.geometry.*;
import javafx.input.*;
import javafx.scene.transform.*;
import javafx.scene.text.*;
import javafx.ext.swing.*;
import java.lang.*;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.animation.Interpolator;

/**
 * @author nakada
 */

public class Barber extends CustomNode {
    attribute x: Integer;
    attribute y: Integer;

    attribute len: Number;
    attribute width: Number;
    attribute baseColor1: Color;
    attribute baseColor2: Color;  
    attribute offset: Number = 0.0;

    attribute animate = Timeline {
        repeatCount: Timeline.INDEFINITE
        keyFrames : [
           at (0s) {offset => 0.0},
           at (1s) {offset => 30.0 tween Interpolator.LINEAR}
        ]
    };
    
    function start() {
        animate.start();
    }
    
    function create(): Node {
        return Rectangle {
            x: bind x
            y: bind y
            width: bind width
            height: bind len
            arcWidth: bind width;
            arcHeight: bind width;
            fill: 
                bind LinearGradient {
                startX: offset
                startY: offset
                endX: 30.0 + offset
                endY: 30.0 + offset
                proportional: false
                cycleMethod: CycleMethod.REPEAT;
                stops: [
                    Stop { offset:  0.0 color: Color.WHITE },
                    Stop { offset:  0.25 color: baseColor1 },
                    Stop { offset:  0.5 color: Color.WHITE },
                    Stop { offset:  0.75 color: baseColor2 },
                    Stop { offset:  1.0 color: Color.WHITE }
                ]
           };
        }
    }
}

var f = SwingFrame {
    content: Canvas {
        content: [
        ]
    }
    background: Color.WHITE
    visible: true
    width: 230
    height: 500
    closeAction: function() { 
        System.exit(0); 
    }
}     
     
var barber = Barber {
    x: 100
    y: 100
    len: 300
    width:  30
    baseColor1: Color.RED
    baseColor2: Color.BLUE
};

insert barber
into (f.content as Canvas).content;

barber.start();