🌊

【Flutter】夏が終わる前におれと波に乗らないか?【アニメーション】

2022/08/31に公開

「みんな、おれと波に乗らねえか?」

ということで、今回作成したアニメーションがこちらです。


可愛くないですか?!!!

Flutterでアニメーションってどうやるんだろう、と思って勉強してみたので簡単にご紹介したいと思います!
アニメーションに関しては色々な記事があってそちらの方が参考になるので記事を貼っておきます! こんなことができるんだという周知のために記事を書いたので細かい実装についてはあまり触れないです!!

実装についてはGitHubにコードを上げておくので読んでみてください!
main.dartにコピペすれば動くよ!
https://github.com/naokiwakata/Flutter_surfer_animation

実装

アニメーションの基礎としてサーファーが上下する部分のみ解説します。波の実装に関しては下記の記事を参考にして作成しました! CustomClipperを用いて波を表現していて非常に面白いです!
https://qiita.com/derodero24/items/590f97ecec2900a947f0

サーファーの実装

サーファーをどうやって上下させてるねんって話ですが簡単です

  • AnimationControllerを準備(0~1の値を無限にリピートできる)
  • AnimationControllerの値を元に三角関数で高さを出す
  • サーファーの画像を用意
  • Positionedでサーファーの高さを変える

AnimationControllerを使用する準備

そのクラスで利用するAnimationControllerが1つだけの場合はSingleTickerProviderStateMixinをmixinしたStateを作成する必要があります。
↓アニメーションについて詳しく知りたい方向け
https://medium.com/flutter-jp/transition-9c57528c84b8

class WavePage extends StatefulWidget {
  const WavePage({Key? key}) : super(key: key);

  
  _WaveState createState() => _WaveState();
}

/// vsync:毎フレームごとに更新を伝えるものmixin
/// SingleTickerProviderStateMixinを適用する必要がある
class _WaveState extends State<WavePage> with SingleTickerProviderStateMixin {
  // アニメーションを制御するコントローラー
  late AnimationController _animationController;

  
  void initState() {
    super.initState();
    //初期化
    _animationController = AnimationController(
      vsync: this, 
      duration: const Duration(milliseconds: 1000),//1秒で0->1に到達
    );
    _animationController.repeat(); //0->1をリピートする
  }

  
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }
  
 
  Widget build(BuildContext context) {
    return Scaffold(
      // AnimatedBuilderでラップし_animationControllerを設定
      body: AnimatedBuilder(
        animation: _animationController,
        builder: (context, child) => ...任意のWidget()
      ),
    );
  }
}

三角関数で高さを出す

いわゆるサインコサインタンジェントのsin関数を使って上下運動を表します

  double f(double b) {
    final height = MediaQuery.of(context).size.height; // 画面の高さ
    // 係数は適当に設定
    final y = math.sin(b * 2 * math.pi) * 30 + height * 0.4;
    return y;
  }

サーファーの画像を用意

サーファークラスを作成します

class Surfer extends StatelessWidget {
  const Surfer({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return SizedBox(
      width: 10,
      child: Image.asset(
        'images/surfing_woman.png',
      ),
    );
  }
}

Positionedで高さを変化させる

0~1を周期的に変化し続ける_animationController.valueを先ほどの三角関数にぶちこんで高さを変化させる

 Positioned(
              left: MediaQuery.of(context).size.width / 2,
              top: f(_animationController.value), // 高さが sin関数に従って変化する
              width: 100,
              child: const Surfer(),
            ),

コピペで動くサーファー

main.dart
import 'package:flutter/material.dart';
import 'dart:math' as math;

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) =>
      MaterialApp(theme: ThemeData(), home: const WavePage());
}

class WavePage extends StatefulWidget {
  const WavePage({Key? key}) : super(key: key);

  
  // ignore: library_private_types_in_public_api
  _WaveState createState() => _WaveState();
}

/// vsync:毎フレームごとに更新を伝えるものmixin
/// SingleTickerProviderStateMixinを適用すれば良い
class _WaveState extends State<WavePage> with SingleTickerProviderStateMixin {
  // アニメーションを制御する
  late AnimationController _animationController;

  
  void initState() {
    super.initState();
    //初期化
    _animationController = AnimationController(
      vsync: this, //お決まり
      duration: const Duration(milliseconds: 1000),
    );
    _animationController.repeat(); //リピート設定
  }

  
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  // サーファーの動きを制御する関数 いわゆる三角関数で上下させる
  double f(double b) {
    final height = MediaQuery.of(context).size.height; // 画面の高さ
    final y = math.sin(b * 2 * math.pi) * 30 + height * 0.4;
    return y;
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: AnimatedBuilder(
        animation: _animationController,
        builder: (context, child) => Stack(
          children: <Widget>[
            // サーファーのアニメーション
            Positioned(
              left: MediaQuery.of(context).size.width / 2,
              top: f(_animationController.value), // 高さが sin関数に従って変化する
              width: 100,
              child: const Surfer(),
            ),
          ],
        ),
      ),
    );
  }
}

class Surfer extends StatelessWidget {
  const Surfer({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return SizedBox(
      width: 10,
      child: Image.asset(
        'images/surfing_woman.png',
      ),
    );
  }
}

波を表示させたい場合はStackで重ねて良い感じに表現しましょう!

さいごに

Flutterでもこういうことができるんだ、ということが知ってもらえたら幸いです! 最後まで読んでいただきありがようございました!!

Flutter大学

Discussion