🐮

【Flutter】バネのようなアニメーションをSpringSimulationで実現する

2023/12/10に公開

SpringSimulation とは

flutter/physics パッケージに含まれています。他には GravitySimulation などもあります。
SpringSimulation ではバネのような動きをシュミレートし、その動きをコントローラーを介しウィジェットに適用することができます。

SpringSimulationの設定例
SpringSimulation(
    springDescription,              // SpringDescription
    _animationController.value,     // start
    1,                              // end
    _animationController.velocity   // velocity(速度)
)

SpringDescription とは

SpringSimulation でシュミレートするためのバネ係数の設定をします。
mass(質量)、stiffness(硬さ)、damping(減衰)が調整できますが、数値に対する動きの変化はとても複雑なので、値を少しずつ変化させながら試すと良いでしょう。

SpringDescriptionの設定例
SpringDescription(
    mass: 1.0,        // 質量
    stiffness: 100.0, // 硬さ
    damping: 10.0,    // 減衰
);

実装してみる

まずはコントローラーの設定から。
upperBound を変更し、コントローラーの値範囲を 0.0 〜 1.2 とします。
これで、0.0 → 1.2 → 1.0 のような値の変化を可能にします。
1.0 → 1.2 の部分がバネで言う伸びている部分です。

controllerの設定
late final AnimationController _animationController;


void initState() {
    super.initState();
    _animationController = AnimationController(
        duration: const Duration(milliseconds: 500),
        vsync: this,
        upperBound: 1.2,
    );
}

次に、アニメーションの実行関数を設定します。
SpringDescription でバネ係数の調整をし、SpringSimulation を作成します。
最後に、animateWith()でシュミレートに従いアニメーションを実行させます。

アニメーションの実行関数
void _animationForward() {
    const springDescription = SpringDescription(
      mass: 0.9,
      stiffness: 190.0,
      damping: 15.0,
    );
    final simulation =
        SpringSimulation(springDescription, _animationController.value, 1, _animationController.velocity);
    _animationController.animateWith(simulation);
}

Widget は AnimatedBuilder を使い動かします。
drive()で 0.0 → 80.0 と値を変化させ動かします。

AnimatedBuilder 横移動
AnimatedBuilder(
    animation: _animationController,
    builder: (context, child) {
        double t = _animationController.drive(Tween(begin: 0.0, end: 80.0)).value;
        return Transform.translate(
            offset: Offset(t, 0),
            child: Container(
                width: 100,
                height: 50,
                decoration: BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(25)),
            ),
        );
    },
)

_animationController.forward()で実行

実行例

_animationController.animateWith(simulation)で実行

実行例

実用例

このようなポップアップはよく見かけるのではないでしょうか。
実行例

実用例
AnimatedBuilder(
    animation: _animationController,
    builder: (context, child) {
        double h = _animationController.drive(Tween(begin: -100.0, end: 0.0)).value;
        return Transform.translate(
            offset: Offset(0, -h),
            child: Container(
                width: 200,
                height: 100,
                decoration: BoxDecoration(
                    color:
                        Colors.red.withOpacity(_animationController.value > 1 ? 1 : _animationController.value),
                    borderRadius: const BorderRadius.all(
                    Radius.circular(10.0),
                    ),
                ),
                child: Center(
                    child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                            Text(
                                'Attention!',
                                style: TextStyle(
                                    fontSize: 24.0,
                                    fontWeight: FontWeight.bold,
                                    color: Colors.white
                                        .withOpacity(_animationController.value > 1 ? 1 : _animationController.value)),
                            ),
                            Text(
                                'validation error',
                                style: TextStyle(
                                    fontSize: 20.0,
                                    color: Colors.white
                                        .withOpacity(_animationController.value > 1 ? 1 : _animationController.value)),
                            ),
                        ],
                    )
                ),
            )
        );
    },
)

Discussion