JavaFXのアニメーション
JavaFXの特徴の一つは強力なアニメーション機構.この書き方も0.7から1.0で大きく変わったらしい.0.7を知らないのでよくわからないが.
TimelineとKeyFrame
1.0では,Timelineというオブジェクトを作ってアニメーションを表現する.TimelineにはKeyFrameオブジェクトを並べる.KeyFrameにはある変数のそのフレームでの値を指定することができる.すばらしいのは最低限のKeyFrameを指定するだけで,あとを補間してくれること.便利だ.Timelineをstart()すると,指定に従ってアニメーションが描画される.
KeyFrameオブジェクトの記法は独特で,=> というオペレータを使う.どうにもセマンティクスが釈然としないのだが,こんな感じに書く.
TimelineオブジェクトのkeyFramesというアトリビュートに二つのKeyFrameを指定している.これは,開始0s後は dyの値を0に,3s後にはdyを200にしろ,という指定になる.
attribute launch = Timeline { keyFrames: [ KeyFrame { time:0s values: dy => 0 }, KeyFrame { time: 3s values: dy => 200 tween Interpolator.EASEOUT } ] };
tweenというのもシンタックスシュガーで,2つのキーフレームの間の補間方法を指定している.EASEOUTは終点部分をゆっくりにするという指定(だと思う).
で,さらに怪しいシンタックスシュガーがあって,こんな風に書くこともできる.at というキーワードを使う.
attribute launch = Timeline { keyFrames: [ at (0s) {dy => 0}, at (3s) {dy => 200 tween Interpolator.EASEOUT} ] };
だいぶコンパクトになるがなにがなんだか.
よく考えてみると => がどう解釈されているのかも釈然としない.ここの"dy"はTimelineが実行されるときに代入される変数になる.クロージャ的に解釈されるのだと思うのだけど,この記法を使わないで書く方法を見つけられなかった.
CustomNode
JavaFXの描画オブジェクトはすべてNodeのサブクラスになるようだ.ユーザが複数のNodeを組み合わせて新たなノードを作る際には,このCustomNodeをつくるのがお作法らしい.どうも,0.7のころにはCompositeNodeという名前だったようだ.
CustomNodeのサブクラスでは,create メソッドを実装しなければならない.このメソッドで,Groupオブジェクトをつくり,その中に子供のNodeをcontentに入れて返す.
class MyNode extends CustomNode{ ... function create(): Node { return Group { content: [ ... ] } } }
Transform
Nodeでは,transform というアトリビュートを指定することができる.transformで親のNodeからの相対位置や回転,スケーリングを指定することができる.さらにaffine変換もできるので,形をゆがめることも可能なはず.
Transform同士の演算方法がAPIを見ても見つけられなかったが,演算できればさらに強力.
サンプル
で,かんたんなアニメーションのサンプルを書いてみた.イメージは花火のつもり.クリックすると打ち上がって,破裂する.せっかくなので動画をキャプチャして貼ってみる.iShowUというシェアウェアを使ったのだけど,登録していないので,画面にでかでかと文字が出ているが,うしろで動いているのだけがサンプルの出力.google videoにアップロードしてみた.なんか便利だなあ...
http://video.google.com/videoplay?docid=3154407052049434018
ソースはこんな感じ.ちょっと面白いのは,打ち上げるアニメーションのtimeline launch の最後のキーフレームで,破裂のtimeline fireを起動している.このように,timelineとtimelineを連携させられるので,結構複雑なアニメーションが割に簡単に書けるかもしれない.
花火は,Circleの集合からなるCustomNodeで表現されている.中心からradius分だけずらしたものをrotateすることで,Circleを円周上に配置している.radius を調整することで花火が広がる.色もalpha値を下げていくことで徐々に背景にとけ込む.
package animationtest; import javafx.application.*; import javafx.input.MouseEvent; import javafx.scene.geometry.*; import javafx.scene.*; import javafx.scene.transform.*; import javafx.scene.paint.*; import javafx.scene.text.*; import javafx.input.*; import javafx.ext.swing.*; import java.lang.System; import javafx.animation.*; class FireWork extends CustomNode{ attribute count = 10; attribute x: Number; attribute y: Number; attribute dy: Number; attribute radius: Number = 0; attribute color: Color = Color.YELLOW; attribute fire = Timeline { keyFrames: [ at (0s) { radius => 0; color => Color.YELLOW }, at (3s) { radius => 60 tween Interpolator.LINEAR; color => Color.rgb(0, 255, 255, 0) tween Interpolator.EASEOUT; } ] } attribute launch = Timeline { keyFrames: [ KeyFrame { time:0s values: dy => 0 }, KeyFrame { time: 3s values: dy => 200 tween Interpolator.EASEOUT timelines: [fire] } ] }; function create(): Node { return Group { transform: bind Transform.translate(x, y - dy); content: for (i in [0 .. count-1]) Circle { centerX: 0, centerY: bind radius radius: 5 fill: bind color transform: Rotate{angle: bind i * (360.0 / count)} blocksMouse: true onMouseClicked: function( e: MouseEvent ):Void { launch.start(); } } } } } var f2 = SwingFrame { content: Canvas { content: [ FireWork{x: 100, y: 300}, FireWork{x: 200, y: 300} ] background: Color.BLACK } visible: true width:300 height:400 }
所感
Timeline強力.各KeyFrameにはactionという属性も定義できて,任意の関数を呼び出すこともできる.つまり,JavaでいうTimerのかわりに使うこともできるようだ.こんな感じで書くと,1秒ごとにactionで指定した関数が実行される.一時停止や再開もできるようだ.
Timeline { repeatCount: Timeline.INDEFINITE KeyFrames: KeyFrame { time: 1s action: function(){ ... } } }
なかなか使い出がありそう.