前回は基本的なアニメーションということで、javafx.animation.Timelineクラスとjavafx.animation.KeyFrameクラスの使い方を紹介しました。今回は、応用編ということで、標準で提供されているさまざまなアニメーションについて紹介していきます。
今回、紹介するアニメーションはすべてjavafx.animation.transitionパッケージで定義されており、javafx.animation.transition.Transitionのサブクラスとなります。Transitionクラスは内部にアトリビュートとしてTimelineオブジェクトを保持しています。このため、Timelineクラスを使わずともアニメーションを実現することができます。
なお、今週使用したサンプルのソースを含めたNetBeansのプロジェクトは下記のリンクよりダウンロードすることができます。
移動、拡大・縮小、そして回転
前回のサンプルで移動のアニメーションを使用したように、移動のアニメーションはもっともよく使われるアニメーションの1つです。その他にも拡大・縮小と回転がよく使用されています。
そのため、これら3種類のアニメーションは、簡単に実現できるように専用のクラスが提供されています。それぞれ、移動がTranslateTransitionクラス、拡大・縮小がScaleTransitionクラス、回転がRotateTransitionクラスとなります。これら3つのクラスの使い方は共通しているので、ここでは回転を行うRotateTransitionクラスを使ってみましょう。
これらのクラスのスーパークラスであるTransitionクラスはnodeアトリビュートを持ちます。このnodeアトリビュートが回転や移動の対象となります。
回転の角度を設定するには、開始角度と回転角度を指定する方法と、開始角度と終了角度を指定する方法があります。開始角度と回転角度は、それぞれfromAngleアトリビュートとbyAngleアトリビュートを設定します。開始角度と終了角度は、fromAngleアトリビュートとtoAngleアトリビュートで設定します。ここでは、青字で示したようにfromAngleアトリビュートとbyAngleアトリビュートを使用しました。
回転の中心はノードの中心です。もしノードの中心以外の点を回転中心にしたい場合は、ダミーの透明なノードで中心を調節するか、RotateTransitionクラスを使わずに直接Timelineクラスを使用するようにします。
durationアトリビュートやrepeatCountアトリビュートなどはTimelineクラスを使用した時と同じです。補間方法はKeyFrameクラスではtween演算子を使用しましたが、ここではinterpolateアトリビュートで指定します。
最後にアニメーションを開始させるため、play関数をコールします。タイムラインを制御するplay関数などの関数もTimelineクラスと同じです。
次にノードを表示する部分を示します。
特に難しい部分は何もありません。単に変数nodeを描画しているだけです。ここでは変数nodeは6つの円としました。
最後のArcオブジェクトがないと、最後の円が一番上になってしまいます。そこで、Arcオブジェクトで最初の円の一部を上書きしています。このようにすることで、すべての円が一部隠れるようになります。
実行結果を図1に示します。いつもと同じようにこの図もアプレットページへのリンクになっています。これ以降も実行結果の図はアプレットのページへのリンクになっています。また、スクリプトも記載しておきますので、参考になさってください。
フェードイン、フェードアウト
次に行うのが、透明から出現したり、透明になって消えていくフェードイン、フェードアウトです。ここで使用するのはFadeTransitionクラスです。FadeTransitionクラスもRotateTransitionクラスと同じようにnodeアトリビュートがフェードイン、フェードアウトの対象となります。
フェードイン、フェードアウトの指定はfromValueアトリビュートとtoValueアトリビュートによって決まります。それぞれ不透明度を表しており、0で透明、1で不透明になります。fromValueアトリビュートの方が値が小さい場合はフェードインになり、大きい場合はフェードアウトになります。
他のアトリビュートはRotateTransitionクラスと同じです。ここでは、フェードイン、フェードアウトを使って星をまばたかせてみました。星をまばたかせるために、まず星形を描画し、その後ろで星形をぼかしたノードに対してフェードイン、フェードアウトを繰り返し行います。
星形を作るにはjavafx.scene.shape.Pathクラスを使用しました。PathクラスはJavaのjava.awt.geom.Path2Dクラスに相当するクラスで任意の直線や曲線を組み合わせてパスを作成します。Path2DクラスがlineToメソッドやcurveToメソッドなどで手続き的にパスを構築するのに対し、PathクラスはLineToクラスやCubicCurveToクラスなどjavafx.scene.shape.PathElementクラスのサブクラスを使用して構造的にパスを構築します。
ぼかしを行うにはNodeクラスのeffectアトリビュートを使用します。effectアトリビュートの型はjavafx.scene.effect.Effectクラスです。Effectクラスのサブクラスにはドロップシャドウを行うDropShadowクラスや、色をセピア調にするSepiaToneクラスなどさまざまなクラスがあります。ぼかしを行うにはGaussianBlueクラスを使用します。
以下に星形と星形をぼかしたノードを作成するスクリプトを示します。
星形を表すPathElementオブジェクトのシーケンスは、星形と星形のぼかしで同一なので、変数starElementsとして括りだしてあります。
最後に星形と星形のぼかしを描画する部分を示します。
夜空を表すため、背景はグラデーションで塗っています。
では、このスクリプトを実行してみましょう。まばたいているように見えるでしょうか?
パスに沿った移動
今まで扱ってきた移動のアニメーションは移動元の座標から、移動先の座標へ直線的に移動するものでした。でも、曲がった軌跡をたどって移動するアニメーションなども行ってみたいと思いませんか。
もちろん、曲線といえども、とても短い区間を取りだしてみれば直線として扱えます。したがって、その短い区間ごとにKeyFrameオブジェクトで軌跡を作り込んでいけば、曲がった軌跡もできなくはありません。しかし、ちょっと考えただけでも、大変そうだと想像がつきます。
Adobe Flashではモーションガイドと呼ばれるパスを設定して、そのパスに沿って移動するアニメーションを作成することができます。同じことを実現するために、JavaFX ScriptではPathTransitionクラスが提供されています。
アニメーションのガイドとなるパスはjavafx.animation.transition.AnimationPathクラスで表します。しかし、AnimationPathクラスのオブジェクトを直接生成することはできません。AnimationPathオブジェクトを生成するにはcreateFromPath関数を使用します。引数の型はPathクラスです。
これをスクリプトで表すと次のようになります。アニメーションはガイドパスの起点から終点までアニメーションしながら移動します。残念ながら、Adobe Flashのようにパスの途中から移動するなどのアニメーションはできませんが、それでもKeyFrameオブジェクトで表すよりは簡単にパスに沿って移動するアニメーションが実現できます。
ここでは花をらせんに沿って移動させてみました。
花は花びらを作成し、90度ずつ回転させて作りました。花びらはPathクラスで表し、ベジェ曲線(CubicCurveToクラス)と円弧(ArcToクラス)を使って作成しています。
ガイドとなるパスはらせんにしました。いわゆる蚊取り線香のようならせんで、一様らせん、もしくはアルキメデスのらせんと呼ばれているらせんです。一様らせんはx=αθsinθ, y=αθcosθで表すことができます。
以下にらせんを作成している部分を示します。角度を15度で分割し、その間の弧を直線で近似させてしまっていますが、それほどカクカクになることはありません。
Stageオブジェクトを作成する部分は今までのサンプルと同じなので、省略させていただきました。最後にアニメーションを行う部分を示します。
赤字で示したorientationアトリビュートはノードがパスに沿って移動する時に、パスの傾きに応じて回転するかどうかを示します。デフォルトでは回転を行わないOrientationType.NONEとなっています。
パスに沿って回転させる場合は、リスト9のスクリプトのようにOrientationType.ORTHOGONAL_TO_TANGENTを指定します。
複数のアニメーションを順番に行う
ここまでは単体のアニメーションを扱ってきましたが、複数のアニメーションを組み合わせたいこともあります。このような場合、SequentialTransitionクラスもしくはParallelTransitionクラスを使用します。
この2つのクラスの違いは、SequentialTransitionクラスが複数のクラスを順々にシーケンシャルに実行するのに対し、ParallelTransitionクラスが複数のアニメーションを同時にパラレルに実行することにあります。
そこで、本節ではSequentialTransitionクラス、次節でParallelTransitionクラスを説明します。
SequentialTransitionクラスはcontentアトリビュートを持ちます。contentアトリビュートの型はTransitionクラスのシーケンスであり、ここに順番に実行するアニメーションを記述していきます。SequentialTransitionオブジェクトのplay関数をコールすると、contentアトリビュートの最初のアニメーションを実行し、終了したら次のアニメーションを実行し、というように順々にアニメーションを行います。
たとえば、次の例では移動、回転、拡大という順番でアニメーションが行われます。
SequentailTransitionクラスのnodeアトリビュートが、contentアトリビュートに指定されたアニメーションの対象になります。contentアトリビュートの要素となる個々のアニメーションでnodeアトリビュートを指定していた場合、個々のアニメーションのnodeが優先されます。
また、特別なアニメーションとして、指定した時間なにも行わないPauseTransitionクラスがあります。シーケンシャルにアニメーションを行う途中に休止時間を設けたい時などに使用することができます。
ここでは、先ほどの星形を四角の縁にあわせて移動させるアニメーションを作ってみました。角に来ると1回転するようにしてあります。さきほどのサンプルのままだと星が大きすぎるので、scaleXアトリビュートとscaleYアトリビュートにそれぞれ0.25を代入して、縮小してあります。
以下にSequentialAnimationクラスを使用する部分を示します。
このスクリプトでは星形を移動、休止、回転させることを繰り返しています。
ここで、一連のアニメーションを繰り返してみようと、SequentialTransitionクラスのrepeatCountアトリビュートを設定して実行してみたのですが、正しく動作しません。SequentialTransitionクラスのAPIドキュメントを見てみると、duration、repeatCountおよびautoReverseは使用できないことが記述されています。
もし、繰り返し実行を行いたい場合は、以下のようにTimelineオブジェクトを作成し、KeyFrameオブジェクトのactionアトリビュートの中でSequentialTransitionオブジェクトのplayFromStart関数を繰り返しコールするようにします。
14秒にKeyFrameオブジェクトがあるのは、14秒のループを作るためです。では、0 秒の時にplayFromStart関数をコールするのではなく14秒の時に行えばいいように感じますが、そうするとサンプルを起動してから14秒間無反応になってしまうのです。
実行結果を図4に示します。
複数のアニメーションを同時に行う
今度は複数のアニメーションを同時に行ってみます。ParallelTransitionクラスの使い方はSequentialTransitionとほぼ同じです。しかし、contentアトリビュートに列記したアニメーションが同時に行われることが異なります。
先ほど作成した花がらせん状に移動するスクリプトは花の回転がちょっと少なすぎたように感じるので、ここではらせんに沿った移動と回転を同時に行ってみましょう。ついでに、花が外側にくるほど大きくなるようにしてみました。
ParallelTransitionクラスもSequentialTransitionクラスと同様duration、autoReverse、repeatCountの各アトリビュートは使用することができません。しかし、同時に実行するアニメーションの時間がすべて同じであれば、個々のアニメーションのautoReverseアトリビュートをtrueにすることでアニメーションを反転させるように見せることができます。
上述のスクリプトではすべてアニメーションの時間が10秒なので、autoReverseをtrueにすることでアニメーションを反転させています。また、アニメーションを繰り返すにはSequentialTransitionクラスと同じように、別個にTimelineクラスを使うことで実現できます。
回転しながららせんに沿って移動する花の実行結果を図5に示しました。
モーフィング
最後におまけ的なアニメーションを紹介しましょう。モーフィングとは2つの形状を自然に変形させることをいいます。
JavaFXでは簡単なモーフィングを行うためにjavafx.scene.shape.DelegateShapeクラスが用意されています。「簡単な」というのは、モーフィングを行う対象がShapeクラスのサブクラスに限定されているからです。
とはいえ、シェイプといえばPathクラスもあるのでスクリプトで任意の形状を定義できます。使い方によってはインパクトのある効果を作り出すこともできるはずです。
DelegateShapeクラスは単独で使用するのではなく、Timelineクラスと共に使うことでモーフィングを行います。たとえば、四角から円にモーフィングさせるには以下のように記述します。
DelegateShapeクラスはshapeアトリビュートを持ち、ここに変形させるシェイプを指定します。シェイプは変化するのでbindをお忘れなく。
そして、Timelineオブジェクトでシェイプを変化させます。ここでは、青字で書いたように四角から円に変化させています。このようにすることでアニメーションで四角から円へ変形を行います。図6は四角から円への変形の途中の段階を示しています。
DelegateShapeクラスを使う時の注意点として、変形させるシェイプオブジェクトではtranslateXアトリビュートやtransformアトリビュートなどの移動、回転、拡大・縮小はすべて無視されるということがあります。そのため、(0, 0)を中心にシェイプを定義するようにするのがお勧めです。また、シェイプオブジェクトでは色も無視されます。これらのアトリビュートはDelegateShapeオブジェクトで指定された値が適用されます。
では、先ほどから何度も登場している星と花を題材にモーフィングしてみましょう。
ところで、星はPathクラスで定義していたのでシェイプですが、花はGroupクラスで定義したのでシェイプではありません。しかし、花びらはPathクラスで定義したので、花びら4枚分をPathクラスで表すのはそれほど大変ではありません。ただし、スクリプトが長くなってしまったので、詳細はリンク先のアプレットのページでご確認ください。
ここでは、モーフィングと一緒に色を変更し、シェイプにはドロップシャドウも付加してあります。また、星と花に円を加えてモーフィングするようにしました。DelegateShapeクラスとTimelineクラスを使用する部分を以下に示します。
星から花、花から丸、丸から星とモーフィングを繰り返します。実行結果を図7に示しました。モーフィングを行っている途中であっても、ドロップシャドウが途中の形状に追従していることがわかります。
このように、単純なシェイプであればモーフィングを行うことが可能です。しかし、少し複雑な形状になると、思ったように形状が変化してくれません。残念ながら、現状のJavaFX Scriptでは変更前のシェイプと変換後のシェイプとの間で対応する箇所を示すヒントをつけることができません。ヒントが使えるようになれば、複雑な形状でも意図したようにモーフィングにすることができるので、活用できる場面も増えるのです。今後のアップデートに期待しましょう。
さて、2回に渡ってアニメーションについて解説してきましたが、いかがでしたでしょうか。アニメーションは効果的に使えばユーザビリティを向上することが可能です。ぜひ、活用していただければと思います。