アニメーション
実装状況:
- 実装済み:
AnimationController(Forward / Reverse / Stop / AnimateWith)、WidgetTicker/ITickerProvider/TickerProviderState<T>、Curve/Curves(標準イージング一式)、InterpolationSimulation、FadeTransition(RenderAnimatedOpacity経由のペイントのみ更新)が動作します。- 未実装:
Tween<T>/CurvedAnimationなどのアニメーション合成、AnimatedContainer系の暗黙的アニメーション、スプリング等の物理シミュレーションは未実装です(ISimulationを実装すればAnimateWithで駆動は可能)。
FloatSoda のアニメーションは Flutter のアニメーション基盤を踏襲しています。Ticker がフレームごとに経過時間を供給し、AnimationController がそれを 0.0〜1.0 の値に変換し、値の変化を購読したものだけが再描画されるという構造です。SetState() によるリビルドを介さずにペイントだけを更新できるため、毎フレームのアニメーションでも Widget ツリーの再ビルドコストがかかりません。
WidgetBinding (IFrameScheduler) │ ScheduleFrameCallback / フレームごとのタイムスタンプ ▼WidgetTicker ← 開始からの相対時間を通知 │ onTick(elapsed) ▼AnimationController : IAnimation<double> │ ISimulation.X(t) で値を計算(Curve 適用) │ Changed / StatusChanged イベント ▼FadeTransition → RenderAnimatedOpacity └─ Changed 購読 → MarkNeedsPaint() → 再ペイントのみ| クラス | 役割 |
|---|---|
IFrameScheduler |
フレームコールバックの登録・キャンセル。通常は WidgetBinding が実装(テストでは Fake に差し替え可能) |
WidgetTicker |
フレームごとに「開始からの相対時間」を通知。Flutter の Ticker 相当 |
ITickerProvider / TickerProvider |
Ticker の生成・追跡。AnimationController.Vsync に渡す |
TickerProviderState<T> |
ITickerProvider を提供する State<T> 基底。Flutter の TickerProviderStateMixin 相当 |
IAnimation<T> |
時間経過で変化する値。Value / Status / Changed / StatusChanged を公開。Flutter の Animation<T> 相当 |
AnimationController |
IAnimation<double> の駆動役。Forward / Reverse / Stop / AnimateWith |
ISimulation |
時間→値の関数。標準実装は InterpolationSimulation(begin→end を Duration と Curve で補間) |
ICurve / Curve / Curves |
イージング曲線。Curves に標準インスタンス一式 |
AnimationController
Section titled “AnimationController”IAnimation<double> の標準実装で、LowerBound(既定 0.0)〜 UpperBound(既定 1.0)の間を Duration かけて往復させます。
var controller = new AnimationController{ Vsync = this, // TickerProviderState<T> を継承した State Duration = TimeSpan.FromSeconds(1.5), Curve = Curves.EaseInOut, // 省略時は Linear};
controller.Forward(); // UpperBound へ再生controller.Reverse(); // LowerBound へ再生controller.Forward(from: 0.5); // 指定値から再生(残り割合で Duration をスケール)controller.Stop(); // 停止(Value は現在値のまま)AnimationStatus
Section titled “AnimationStatus”Status は値がどちら側にあるか・どちらへ進行中かを表します。
| Status | 意味 |
|---|---|
Dismissed |
停止・先頭(LowerBound) |
Forward |
順方向に進行中 |
Reverse |
逆方向に進行中 |
Completed |
停止・末尾(UpperBound) |
StatusChanged を購読すると完了・折り返しを検知できます。往復アニメーションは Completed で Reverse()、Dismissed で Forward() を呼ぶのが定石です(samples/FloatSoda.Samples.OverlayApp/PulseWidget.cs 参照)。
AnimateWith(カスタムシミュレーション)
Section titled “AnimateWith(カスタムシミュレーション)”Forward / Reverse は内部で InterpolationSimulation を使いますが、AnimateWith(ISimulation) に任意の ISimulation 実装を渡せば、スプリングなど時間関数が非線形なアニメーションも駆動できます。
Ticker と TickerProvider
Section titled “Ticker と TickerProvider”AnimationController は自分では時計を持ちません。Vsync に渡された ITickerProvider から WidgetTicker を生成し、フレームコールバック(IFrameScheduler、通常は WidgetBinding)経由でタイムスタンプを受け取ります。
State でコントローラを使う場合は TickerProviderState<T> を継承するのが最も簡単です:
public record PulseState : TickerProviderState<PulseWidget>{ private AnimationController? _opacity;
public override void InitState() { _opacity = new AnimationController { Vsync = this, Duration = TimeSpan.FromSeconds(1.5), }; _opacity.Forward(); }}WidgetTicker は Muted = true で(経過時間の基準を保ったまま)一時停止できます。Dispose() で Provider の追跡から外れます。
Curve と Curves
Section titled “Curve と Curves”ICurve.Transform(t) は正規化された時刻 t∈[0,1] を写像します。抽象基底 Curve は Flutter と同じ契約(端点 t==0 / t==1 はそのまま返し、間だけ TransformInternal に委譲)を保証し、Flipped で時間・値軸を反転したカーブを得られます。
| 型 | 説明 |
|---|---|
LinearCurve |
恒等写像 |
Cubic(a, b, c, d) |
3次ベジェ。CSS の cubic-bezier 相当 |
ThreePointCubic |
2つの Cubic を中点で連結(強調イージング用) |
SawTooth(count) |
0→1 を count 回繰り返すノコギリ波 |
Interval(begin, end, curve) |
指定区間だけ curve を適用、区間外はクランプ |
Threshold(t) |
t 未満は 0、以上は 1 のステップ |
FlippedCurve(curve) |
1 - curve(1 - t) |
DecelerateCurve |
放物線状の減速 |
ElasticIn/Out/InOutCurve(period) |
ゴムひものような弾性振動 |
BounceIn/Out/InOutCurve |
ボールが跳ねるようなバウンス |
標準インスタンス(Curves)
Section titled “標準インスタンス(Curves)”Flutter の Curves と同じ係数で、命名は C# 規約(PascalCase)です。
| グループ | メンバ |
|---|---|
| 基本 | Linear, Decelerate, FastOutSlowIn, SlowMiddle |
| Ease | Ease, EaseIn, EaseOut, EaseInOut, EaseInToLinear, LinearToEaseOut |
| Ease バリエーション | EaseIn/Out/InOut × Sine / Quad / Cubic / Quart / Quint / Expo / Circ / Back(例: EaseInOutCubic) |
| 強調・複合 | EaseInOutCubicEmphasized, FastLinearToSlowEaseIn, FastEaseInToSlowEaseOut |
| バウンス | BounceIn, BounceOut, BounceInOut |
| 弾性 | ElasticIn, ElasticOut, ElasticInOut |
Back 系と Elastic 系は 0〜1 の範囲を行き過ぎる(オーバーシュートする)値を返します。AnimationController は LowerBound〜UpperBound で値をクランプするため、オーバーシュートをそのまま使いたい場合は注意してください。
FadeTransition と RenderAnimatedOpacity
Section titled “FadeTransition と RenderAnimatedOpacity”FadeTransition は IAnimation<double> で子の不透明度を駆動するウィジェットです。
new FadeTransition{ Opacity = _controller, // IAnimation<double> Child = ...,}ポイントは更新経路です。RenderAnimatedOpacity が Changed を購読し、値が変わったフレームだけ MarkNeedsPaint() を呼びます(RenderObjects の差分更新参照)。つまり:
- Widget のリビルドは発生しない —
SetState()は不要 - レイアウトも走らない — 再ペイントのみ(
OpacityLayerの差し替え) - 不透明度が 0 のフレームは子のペイント自体をスキップ
毎フレーム値が変わるアニメーションで SetState() を使うとフレームごとに Widget ツリーの差分ビルドが走るため、アニメーション値は *Transition 系ウィジェット(現状は FadeTransition)で RenderObject に直結させるのが推奨パターンです。
テストでの駆動
Section titled “テストでの駆動”IFrameScheduler を Fake に差し替えると、実時間なしでアニメーションを進められます(tests/FloatSoda.Test/Animation/ の FakeFrameScheduler 参照):
var scheduler = new FakeFrameScheduler();var provider = new TickerProvider { ResolveScheduler = () => scheduler };var controller = new AnimationController { Vsync = provider, Duration = TimeSpan.FromSeconds(1) };
controller.Forward();scheduler.Pump(TimeSpan.Zero); // 基準点scheduler.Pump(TimeSpan.FromSeconds(0.5)); // Value == 0.5- WidgetSystem —
StatefulWidget/Stateのライフサイクル - BuildPipeline — フレームパイプラインと
WidgetBinding - RenderObjects —
MarkNeedsPaintによる差分ペイント - APIDesign —
Changed/StatusChangedなどイベント通知の規約