(このページは docs.flutter.dev(https://docs.flutter.dev/ui/animations/overview)のリファレンスブログです。)
Flutter のアニメーションシステムは、型付きの Animation オブジェクトを基盤にしています。ウィジェットは、これらのアニメーションをビルド関数内で直接利用するか、アニメーションの現在の値を読み取り、その状態の変化を監視することで組み込むことができます。また、これらのアニメーションを元にして、さらに複雑なアニメーションを作成し、そのアニメーションを他のウィジェットに渡して使用することも可能です。
1.Animation クラス
アニメーションシステムの基本構成要素は、Animation クラスです。このクラスは、アニメーションのライフタイムにわたって変化する特定の型の値を表します。多くのウィジェットは、アニメーションを実行するために Animation オブジェクトをパラメータとして受け取り、そこから現在のアニメーションの値を読み取り、値の変化を監視します。
(1)addListener メソッド
アニメーションの値が変わるたびに、addListener メソッドで追加されたリスナーに通知が送られます。通常、アニメーションを監視する State オブジェクトは、このリスナーで setState を呼び出して、ウィジェットシステムに「アニメーションの新しい値で再描画が必要」であることを通知します。
このようなパターンは非常に一般的で、Flutter にはアニメーションの値が変わったときにウィジェットを再描画するための AnimatedWidget と AnimatedBuilder という2つのウィジェットが用意されています。
(a)AnimatedWidget
アニメーション付きのステートレスウィジェットに便利です。
これを使用するには、AnimatedWidget を継承し、build 関数を実装します。
(b)AnimatedBuilder
複雑なウィジェットで、アニメーションを含むビルド関数を構成する際に便利です。AnimatedBuilder を構築して builder 関数を渡すだけで利用できます。
(2)addStatusListener メソッド
アニメーションには、時間経過に応じてアニメーションの状態を示す AnimationStatus もあります。この状態が変わると、addStatusListener で追加された全てのリスナーに通知が送られます。
アニメーションは通常、「dismissed 状態」で開始します。この状態は、アニメーションが範囲の開始地点(例えば、0.0 から 1.0 のアニメーションの場合、値が 0.0)にあることを示します。アニメーションはその後、forward(0.0 から 1.0 に向かう)または reverse(1.0 から 0.0 に向かう)のいずれかの方向に進行します。アニメーションが範囲の終点(1.0)に達すると、completed 状態になります。
このように、AnimationStatus によってアニメーションの進行方向や状態を制御・監視できます。
2.AnimationController
アニメーションを作成するためには、まず AnimationController を作成します。AnimationController は、自身がアニメーションであると同時に、そのアニメーションを制御する役割も果たします。例えば、アニメーションを順方向に再生したり、停止したりする指示ができます。また、「フリング」アニメーションのように物理シミュレーション(バネなど)を使用してアニメーションを駆動させることも可能です。
AnimationController を作成した後、それを基礎にして他のアニメーションを構築することもできます。例えば以下のような応用が可能です。
(1)ReverseAnimation
オリジナルのアニメーションを反転させて、逆方向に進行するアニメーション(1.0 から 0.0 へ進む)を作成します。
(2)CurvedAnimation
カーブ(Curve)を適用してアニメーションの値がより滑らかになるように調整するアニメーションを作成します。
CurvedAnimation は、例えば easeInOut カーブなどを使用してアニメーションに緩急をつけ、自然な動きにするために使用されます。
この様に、AnimationController は、アニメーションを制御・操作するための中心的な役割を持ち、単独で使用できるだけでなく、他のアニメーション(反転やカーブ)を構築する基礎としても活用できる非常に柔軟なツールです。
3.Tweens
(1)概要
0.0 から 1.0 以外の範囲でアニメーションさせたい場合に使用され、begin と end の値の間を補間するオブジェクトです。Tween は、開始値と終了値の間を線形補間(線形に変化)することで、アニメーションの中間の値を生成します。特定の型向けの補間を提供するサブクラスがいくつか用意されており、例えば ColorTween は色の補間を行い、RectTween は矩形の補間を行います。独自の補間を行いたい場合は、Tween クラスを継承し、lerp 関数をオーバーライドしてカスタム補間を実装できます。
(2)Tween 値の取得方法
Tween は単独では単に「補間の方法」を定義しているだけで、アニメーションの現在のフレームに対応する具体的な値は持ちません。実際のフレームの値を取得するためには、アニメーションの現在の状態に基づいて Tween を評価する必要があります。Tween と Animation を組み合わせることで、現在の値を取得する方法は主に2つあります。
(a)アニメーションの現在値を使って Tween を評価する
- これは、すでにanimation をリッスンしていて、その値が変化するたびにウィジェットが再構築されるようなケースで便利です。
- 例えば、animation の現在値を使って Tween の補間を評価し、その値を直接使用することができます。
(b)アニメーションに基づいて Tween をアニメートする
- Tween の animate メソッドを使うと、新しい Animation を返します。この Animation は Tween を組み込んだもので、単一の値を返すのではなく、アニメーション全体にわたる補間値を保持します。
- この方法は、生成したアニメーションを他のウィジェットに渡して、Tween を組み込んだ現在の値を読み取ったり、その値の変化をリッスンさせたりする場合に役立ちます。
4.Architecture(構造)
アニメーションは、実際にはいくつかのコアとなる構成要素から構築されます。
(1)Scheduler
SchedulerBinding は、スケジューリング(スケジュール管理)の基本機能を提供するシングルトンクラスです。
(特に「フレームコールバック(frame callbacks)」という重要な機能が含まれており、Flutterエンジンが画面にフレームを表示するたびに、この「フレームコールバック」がトリガーされます。)
(a)フレームコールバックの詳細
① フレームコールバックの発生
- Flutterエンジンが「フレーム表示」処理時、コールバック(「begin frame」(フレーム開始))が発生します。
- SchedulerBinding は、scheduleFrameCallback() を使って登録されたリスナー(コールバック関数)にこの「フレーム開始」コールバックを同時に通知します。
② フレームのタイムスタンプ
- 各フレームには、基準時点(EPOC)からの経過時間として タイムスタンプ が Duration の形で付与されます。
- フレーム内で複数のアニメーションが実行される場合も、このタイムスタンプが同じであるため、ほんの数ミリ秒の遅れが生じたとしても、それらが同時に発生しているように表示され、アニメーションの同期が保たれます。
(b)フレームコールバックによるアニメーションの同期
このタイムスタンプ機能により、複数のアニメーションをフレーム単位で同期させることが可能です。
(例えば、scheduleFrameCallback() を使用して複数のアニメーションを同時に開始した場合、たとえそれぞれのコールバックが実行されるタイミングにわずかな遅延があっても、各アニメーションは同じフレームタイムスタンプを基準に開始されるため、視覚的には完全に同期されたように見えます。)
(2)Tickers
Ticker クラスは、スケジューラが提供する scheduleFrameCallback() 機能を利用して、定期的にコールバックを呼び出す仕組みを提供します。アニメーションや時間をベースにした処理を「刻み(tick)」と呼ばれる定期的な間隔で実行する際に使われます。
(a)Ticker の基本的な動作
① 開始と停止
- Ticker は開始(start)と停止(stop)が可能で、開始されると、停止するまでの間、毎回の「tick」ごとにコールバックが呼び出されます。
- 開始すると Future を返し、この Future は Ticker が停止された時に完了(resolve)します。
② 経過時間の提供
- 各「tick」で、Ticker はコールバックに、最初の「tick」からの経過時間を Duration の形で渡します。
- これにより、アニメーションや時間の経過に応じた処理が、正確に開始時からの経過時間を基準にして進行できます。
(b)Ticker の同期機能
① 同期された動作
- Ticker は常に最初の「tick」からの相対的な経過時間を返すため、複数の Ticker が異なるタイミングで開始されても、全てが同期された形で「tick」を刻みます。
(例えば、3つの Ticker をあるフレーム内で異なるタイミングで開始した場合でも、全てが同じ開始時刻を基準とし、同時に「tick」するようになります。)
② バス停の比喩
- Ticker は、バス停で人々が定期的に到着するバスを待っているのと同じ様に、定期的な「tick」イベントが発生するまで待機し、開始時刻が一致した状態で進行します。
(c)実用面でのメリット
この同期機能により、複数のアニメーションや時間ベースの処理を正確に揃えて進行させることができるため、アニメーションの統一感が生まれ、複数の Ticker を使った際にもずれることなく動作します。
(3)Simulations
Simulation は抽象クラスで、相対的な時間(経過時間)を double 型の値にマッピングする仕組みを提供します。
また、Simulation には「完了」の概念も含まれており、アニメーションやモーションが一定の条件で終了するかどうかを判定できます。
(a)シミュレーションの原則と実際の状態管理
① 理論上はステートレス
- 基本的に Simulation 状態を持たず、同じ経過時間を入力すれば常に同じ結果を返すべきものとされています。
② 実際のシミュレーションでは状態が変わる場合も
- しかし、BouncingScrollSimulation や ClampingScrollSimulation などの一部のシミュレーションでは、クエリ(状態の問い合わせ)が行われると不可逆に状態が変化するため、状態が保存されます。
(b)Simulation クラスの具体的な実装
Simulation クラスにはさまざまな具体的な実装があり、それぞれ異なる効果や動作を実現します。
(例えば、以下の様なシミュレーションが含まれています。)
① BouncingScrollSimulation
- スクロールがバウンドするような効果をシミュレーションします。
リストやスクロールビューでスクロール範囲外に移動しようとした時の反発の様な動きを表現します。
② ClampingScrollSimulation
- スクロール範囲外に行かないようにスクロールを制限するシミュレーションです。
範囲外でのバウンド効果を抑え、スクロールがすぐに制限される動作を表現します。
(c)利用シーンとメリット
Simulation クラスは、アニメーションやモーションに物理的な挙動を追加する際に役立ちます。
経過時間を基にした自然な動作や制限付きの動作、反発効果など、インタラクティブなアニメーションにリアルな手応えを加えることができます。
(4)Animatables
Animatable抽象クラスは、doubleを特定の型の値にマッピングします。
Animatableクラスはステートレスで不変です。
(a)Tweens
Tween<T> は、0.0〜1.0 の範囲にある double 型の値を、指定された型 T の値にマッピングする抽象クラスです。(例えば色(Color)や別の double 値など、特定の型の範囲を補間して返すために使われます。)
Tween クラスは Animatable の一種でもあり、アニメーションの補間を柔軟に実現します。
① Tween の基本要素
(ⅰ)出力型(T)
- Tween クラスは型パラメータ T を持っており、補間される値の型を指定します。
(例: Color、Rect、double など)
(ⅱ)開始値( begin )と終了値( end )
- Tween には begin と end の2つの値があり、これらの間を補間します。
(例:入力値が 0.0 の時は begin を返し、1.0 の時は end を返します。)
(ⅲ)補完
- lerp(線形補間)関数を使い、0.0〜1.0 の範囲の入力に応じて begin と end の間の値を計算します。
(例:入力値が 0.5 の場合、begin と end の中間の値が返されます。)
② Tween の特性
- ステートレスでイミュータブル:
- Tween クラスは状態を持たない(ステートレス)ため、インスタンスが生成された後は begin と end の値が変わることはありません。
(この性質により、同じ Tween インスタンスが安全に再利用できます。)
- Tween クラスは状態を持たない(ステートレス)ため、インスタンスが生成された後は begin と end の値が変わることはありません。
③ 利用シーン
Tween は、アニメーションにおいて進行度(0.0 から 1.0 の範囲)に応じて色や位置、サイズなどをスムーズに変化させたい場合に役立ちます。
(具体的には、Tween をアニメーションの Animation に組み合わせることで、ウィジェットが滑らかに変化するインタラクティブなアニメーションが可能になります。)
(b)Composing animatables
Animatable を他の Animatable に渡して chain() メソッドを使用すると、親のマッピングを適用した後に子のマッピングを適用する新しい Animatable サブクラスが作成されます。この仕組みにより、複数の Animatable を組み合わせ、より複雑なアニメーションの変換や補間が可能になります。
① chain() メソッドの仕組み
(ⅰ)親 Animatable のマッピング
- 最初に、親の Animatable が定義するマッピング(0.0〜1.0 の範囲に基づく補間や変換)が適用されます。
(例えば、進行度 0.0 から 1.0 までの範囲を Tween や Curve で変換するようなアニメーションが親 Animatable で設定されているとします。)
(ⅱ)子 Animatable のマッピング
- 親のマッピング結果が子の Animatable に渡され、次の変換が適用されます。
こうすることで、変換や補間が連続して行われ、複数の効果が合成されます。
② 利点と利用シーン
chain() を使った Animatable の合成により、例えば次の様なアニメーションが簡単に作成できます。
- カスタムな動きの曲線
親 Animatable で基本的な Tween を設定し、子に CurvedAnimation を指定すると、滑らかな動きのカーブが加わります。 - 複数の補間を組み合わせたアニメーション
あるアニメーションの進行度を別のアニメーションに引き渡すことで、非線形な動きや複雑なアニメーションシーケンスを構築できます。
この様に chain() メソッドを活用すると、単一の Animatable よりも多様でダイナミックなアニメーション表現が可能になります。
(c)Curves
Curve は抽象クラスで、0.0〜1.0 の範囲にある double 型の値を別の 0.0〜1.0 の範囲に変換するためのものです。
アニメーションの進行に対してカーブを適用することで、動きに緩急をつけたり、非線形な効果を加えたりするために使用します。
① Curve の特性
- ステートレスでイミュータブル:
- Curve クラスは状態を持たない(ステートレス)ため、作成された後もその状態が変わることはありません。この性質により、安全に再利用できるため、同じ Curve を複数のアニメーションに適用可能です。
② 使い方の例
Curve は、アニメーションの値に対して「進行の曲線」を加えることで、単調な直線的な変化ではなく、自然で滑らかな動きを実現します。代表的なカーブには以下があります。
- Curves.easeIn:
ゆっくり開始し、徐々に速くなる動き - Curves.easeOut:
素早く開始して、ゆっくりと止まる動き - Curves.easeInOut:
開始と終了がゆっくりで、中間が速い動き
③ 利用シーン
Curve は、アニメーションの動きにリアルな手触り感を加えるために使用されます。
(例えばボタンの拡大・縮小やリストのスクロール動作に適用することで、自然で見栄えの良い動きを演出できます。)
(d)Animations
Animation は抽象クラスで、指定された型の値、アニメーションの方向、アニメーションの状態(ステータス)を提供します。また、アニメーションの値や状態が変化したときに呼び出されるコールバック関数を登録するためのリスナーインターフェースも備えています。
① Animation の主な機能
(ⅰ)値と方向の提供
- Animation クラスは指定された型の値を提供し、その進行方向(前進または逆方向)も保持します。
- AnimationStatus も持ち、アニメーションが開始、停止、完了、または戻されているかなどの状態を管理します。
(ⅱ)コールバックの登録
- アニメーションの進行に応じて値や状態が変化すると、登録されたコールバックが呼び出されます。
- これにより、アニメーションの状態をリアルタイムで反映してウィジェットを更新することが可能です。
② 特殊な Animation サブクラス
(ⅰ)値が変化しない(固定)アニメーション
- kAlwaysCompleteAnimation、kAlwaysDismissedAnimation、AlwaysStoppedAnimation など、値が変わらない特殊なサブクラスが用意されています。これらは一度もコールバックが呼ばれない為、コールバックの登録が効果を持たない固定アニメーションです。
(ⅱ)Animation
- Animation は特別な用途を持っており、0.0〜1.0 の範囲の double 値を表現します。
これは Curve や Tween の入力として想定される範囲であり、これらと組み合わせてアニメーションを表現する際に多用されます。
③ Animation の状態管理
- 一部の Animation サブクラスはステートレスで、親 Animation にリスナーを渡す機能だけに特化しています。
- 一方、別のサブクラスはアニメーションの進行状況や状態を追跡する為、非常にステートフル(状態を持つ)です。
④ 利用シーン
Animation は、アニメーションの方向や進行状況に応じてウィジェットの動作を調整する際に活用されます。
(特に Animation を使用すると、アニメーションの進行に応じてウィジェットが滑らかに変化するエフェクトを簡単に実現できます。)
(e)Composable animations
Composable Animations とは、親アニメーション(Animation)をベースにしたさまざまな派生アニメーションを組み合わせて使用できる仕組みのことです。これにより、アニメーションの進行を簡単にカスタマイズし、複雑なアニメーションを構築することができます。
以下は主要な Animation サブクラスについての説明です。
① CurvedAnimation
(ⅰ)機能
- 親の Animation の値を基に、カーブ(Curve)を適用した結果を出力します。
(例えば、前進時には Curves.easeIn を、逆方向では Curves.easeOut を適用することが可能です。)
(ⅱ)特徴
- ステートレス(状態を持たない)で、イミュータブル(不変)です。
- アニメーションの進行に対して、非線形な動き(緩急)を加えるために使用されます。
(ⅲ)利用例
- ボタンのクリック時にゆっくり拡大する効果や、スクロールビューの滑らかな動きの実現。
② ReverseAnimation
(ⅰ)機能
- 親の Animation を基に、その値と状態を反転して出力します。
(例えば、親が 0.0 から 1.0 に進む場合、ReverseAnimation は 1.0 から 0.0 に進みます。)
(ⅱ)特徴
- ステートレスで、イミュータブルです。
- アニメーションの方向や状態(AnimationStatus)も逆転します。
(ⅲ)利用例
- アニメーションを逆再生する必要がある場面で利用します。
③ ProxyAnimation
(ⅰ)機能
- 親の Animation の状態(値やステータス)をそのまま転送します。
(親アニメーションが変更可能(ミュータブル)であることを前提としています。)
(ⅱ)特徴
- 親の状態を透過的に利用する為、他のアニメーションの動作をラップしたり、動的に切り替える用途に適しています。
(ⅲ)利用例
- 別のアニメーションに柔軟に変更可能なアニメーションを実現。
④ TrainHoppingAnimation
(ⅰ)機能
- 2つの親アニメーションを受け取り、それらの値が交差した時にアニメーションを切り替えます。
(ⅱ)特徴
- アニメーションのスムーズな連続性を保ちながら、2つの異なるアニメーションを切り替える特殊なケースに対応します。
(ⅲ)利用例
- 例えば、スクロール位置に応じて異なるアニメーションを連続的に適用する場合。
5.Animation controllers
AnimationController は、Animation を実装した状態を持つ(ステートフル)クラスであり、Ticker を利用してアニメーションを駆動します。このコントローラーを使用することで、アニメーションの開始、停止、リピート、特定の値への移動などを柔軟に制御できます。
(1)基本動作
(a)時間を利用した値の取得
- AnimationController は、開始時からの経過時間を基に、指定された Simulation を使用して値を取得します。
- この値が現在のアニメーションの状態として報告されます。
- もし Simulation が指定の時間で終了したと判断すると、コントローラーは自動的に停止します。
(b)範囲の指定
- lowerBound(下限)と upperBound(上限)を指定することで、アニメーションが進行する範囲を定義できます。
- アニメーションの持続時間(duration)も設定可能です。
(2)主要メソッド
(a)forward() と reverse()
- forward():
下限から上限へ線形補間(リニアインターポレーション)でアニメーションを進めます。 - reverse():
上限から下限へ逆方向に進めます。 - どちらも、指定された duration 内で補間を行います。
(b)repeat()
- 下限と上限の間を線形補間で繰り返し続けるアニメーションを実行します。
- 明示的に停止するまでアニメーションは終了しません。
(c)animateTo(double target)
- 現在の値から指定された target 値まで線形補間でアニメーションを進行します。
- duration を指定しない場合、コントローラーのデフォルトの持続時間と範囲を基に速度が計算されます。
(d)fling()
- 物理的な「力(Force)」を使用して Simulation を生成し、それを用いてコントローラーを駆動します。
- スプリングや慣性など、リアルな物理シミュレーションを再現する際に使用します。
(e)animateWith(Simulation simulation)
- 明示的に指定した Simulation を用いてアニメーションを駆動します。
- Simulation を直接操作したい場合に便利です。
(3)戻り値
- 上記のメソッドは全て、Ticker が提供する Future を返します。
- この Future は、コントローラーが停止したり、次のシミュレーションに切り替わると解決されます。
(4)まとめ
AnimationController は、Flutter のアニメーションシステムの中心的な存在です。
以下の様な場面で非常に役立ちます。
- アニメーションの進行を完全に制御したい場合。
- シンプルな線形補間から、物理シミュレーションを用いたアニメーションまで幅広く対応したい場合。
- アニメーションの途中で動的にターゲットや方向を変更したい場合。
これにより、ユーザーインタラクションに応じたダイナミックなアニメーション表現を柔軟に実現できます。
6.Attaching animatables to animations
Animation を Animatable の animate() メソッドに渡すと、新しい Animation サブクラスが作成されます。
このサブクラスは、Animatable の動作を保持しつつ、指定された親アニメーション(Animation)によって駆動されます。
(1)動作の仕組み
(ⅰ)親アニメーション(Animation)の指定
- Animatable は通常、自身が補間(マッピング)を提供する役割を持っています。
(その animate() メソッドに親となるアニメーション(Animation)を渡すと、その親アニメーションの進行状況(0.0〜1.0)に基づいて自身の補間を行います。)
(ⅱ)新しい Animation サブクラスの生成
- animate() メソッドを使うことで、新しい Animation サブクラスが作成されます。
(この新しいアニメーションは、元の Animatable の補間機能をそのまま保持しつつ、親アニメーションに基づいて動作します。)
(ⅲ)親アニメーションに駆動される
- 作成されたアニメーションは、親アニメーションの進行(たとえば、AnimationController が管理する進行度)によって駆動され、対応する値を出力します。
(2)利用例
以下のようなコードで animate() メソッドが使われます
final Animation<double> parentAnimation = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
final Animatable<Color> colorTween = ColorTween(
begin: Colors.red,
end: Colors.blue,
);
//Animatable<Color>:
//( ColorTween:
// Animatable<Color> の一例で、begin から end の色を補間します )
//生成されたアニメーション (animatedColor):
//( animatedColor:
// 親アニメーションに駆動され、現在の進行状況に基づいた色を出力します )
//親アニメーション (parentAnimation):
//( ここでは、AnimationController が parentAnimation として利用されています。
// 進行状況(0.0 から 1.0)を提供します )
final Animation<Color> animatedColor = colorTween.animate(parentAnimation);