🦁

【最短理解】Flutter animationを用いてWidgetを移動させる

2023/06/17に公開

今回は、flutterのアニメーションの仕組みと使い方を簡潔にまとめました。

この記事を読むことで、Widgetを『好きな場所に』『好きなように』移動させる事ができるようになります。また、アニメーションをまだ実装した事がない人でも簡単に実装できるように解説もつけております。

✅今回の内容

  1. アニメーションの仕組み
  2. アニメーションを作るためのパーツ
  3. animationをWidgetに反映させる
  4. 実際に動かしてみる
  5. まとめ

✅メイン内容

🛠 アニメーションの仕組み

1. アニメーションはどうやって動いているのか

結論:仕組み自体はパラパラ漫画と変わらない。

Youtubeや映画などの映像・動画は、『パラパラ漫画』と同じ仕組みで出来ています。
少し違う静止画を高速で何枚も表示させる事で、まるで動いているかのように見えます。

flutterでも同じように静止画(Widget)を何枚も表示させる事で、
まるでWidgetが動いているかのように見せる事ができます。


🛠 アニメーションを作るためのパーツ

flutterでアニメーションを作るためには最低限、下記3つが必要になります。

  • AnimationController
    • アニメーションの進行具合
  • Tween
    • アニメーションの始点と終点の位置
  • animation
    • 実際にWidgetに反映させるためのanimationそのもの

1. AnimationController

AnimationControllerは、アニメーションの進行具合を管理します。
durationには、アニメーションに掛かる時間を設定します。今回は0.5秒に設定しています。
vsyncには、毎フレームごとに画面の更新を伝えてくれるオブジェクトを設定します。
ここのthisSingleTickerProviderStateMixinになります。(後ほど説明

AnimationController _controller = AnimationController(
      duration: Duration(milliseconds: 500),
      vsync: this,
    );

2. Tween

Tweenで、アニメーションの始点と終点を設定します。
beginには、開始位置を設定します。今回はAlignment(0.0, 0.0)としています。
endには、終了位置を設定します。今回はAlignment(1.0, 1.0)としています。

Tween<Alignment> tween = Tween(
      begin: const Alignment(0.0, 0.0),
      end: const Alignment(1.0, 1.0),
    );

ここで使用しているAlignmentではx座標とy座標をそれぞれdouble型で指定します。
少し注意が必要なのは、座標の取り方です。

ContainerTextなど多くのWidgetでは座標が x= 0.0,y= 0.0 であれば、
画面の左上を取ります。

このAlignmentではx= 0.0,y= 0.0であれば親Widgetの中央を取ります。
また、上下左右の端はそれぞれx= -1.0,y= -1.0x= 1.0,y= 1.0
座標を指定する事ができます。

今回の場合は、開始地点が親Widgetの中央で、終了地点が親Widgetの右下です。


3. animation

animationで、実際に上記で設定したアニメーションをWidgetに反映させます。

Animation<Alignment> animation = _controller.drive(tween);

簡単にまとめると

animation = AnimationController × Tween

という事です。


🛠 animationをWidgetに反映させる

animationをWidgetに反映させる方法は2つあります。

  • AnimatedBuilder
  • AnimatedWidget

今回は、AnimatedBuilderのみ解説します。
AnimatedWidgetについてはまた今度解説します。

1. AnimatedBuilder

builderの解説
animationパラメータは画面更新の通知を受けて、builderを走らせます。
画面更新の通知はAnimationControllerで受け取る事ができるので、
このanimationパラメータには先ほどのanimation変数を置いておきます。
これにより、毎フレームごとにbuilderが走ります。

Alignの解説
alignmentパラメータにはanimation.valueを渡しています。
alignmentパラメータは、先ほど作成したanimationを反映させたいWidgetに紐付けます。
これによりAlignWidgetが開始地点から終了地点に移動するまで画面が更新され続けます。

AnimatedBuilder(
          animation: animation,
          builder: (context, _) {
            return Align(
              alignment: animation.value,
              child: Container(
                width: 200,
                height: 200,
                color: Colors.purple[200],
              ),
            );
          },
        ),

これでWidgetにanimationを反映させる事ができました。🍎


🛠 実際に動かしてみる

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Alignment> animation;
  bool mode_change_switch = true;

  Future<void> setAnimation() async {
    _controller = AnimationController(
      duration: Duration(milliseconds: 500),
      vsync: this,
    );
    Tween<Alignment> tween = Tween(
      begin: const Alignment(0.0, 0.0),
      end: const Alignment(1.0, 1.0),
    );
    animation = _controller.drive(tween);
  }

  
  void initState() {
    setAnimation();
    super.initState();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter app'),
      ),
      body: Container(
        width: double.infinity,
        height: (MediaQuery.of(context).size.height / 2) -
            AppBar().preferredSize.height,
        color: Colors.amber,
        child: AnimatedBuilder(
          animation: animation,
          builder: (context, _) {
            return Align(
              alignment: animation.value,
              child: Container(
                width: 200,
                height: 200,
                color: Colors.purple[200],
              ),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          mode_change_switch ? _controller.forward() : _controller.reverse();
          mode_change_switch = !mode_change_switch;
        },
        backgroundColor: Colors.green,
        child: Icon(
          Icons.animation,
          color: Colors.white,
        ),
      ),
    );
  }
}

画面生成時に、setAnimationメソッドを実行してanimationを作成します。
今回は、floatingActionButtonを押して、Containerを移動させます。
一度移動した後に、元の位置に戻すためにmode_change_switchフラグで状態を管理してます。

一応、onPressed内の処理の解説をしておきます。

mode_change_switch ? _controller.forward() : _controller.reverse();
mode_change_switch = !mode_change_switch;

最初にボタンが押された時、フラグは初期値がtrueなので、
_controller.forward()で先ほど設定したanimationを実行します。
その後、フラグの値を逆にします。

この時、Containerは中央から右下に移動しています。

2回目にボタンが押された時は、Containerが中央に戻るように実装したかったので、
今回は_controller.reverse()を実行してanimationを逆再生しています。

📍 SingleTickerProviderStateMixin

AnimationControllerの解説で(後ほど説明)としていた箇所です。
このSingleTickerProviderStateMixinは何かというと、
端末固有のフレームレートを教えてくれて、フレーム毎に画面更新の通知を渡してくれるクラスです。
このクラスのおかげでAnimationControllerは画面の更新を受け取る事ができます。

実際に、durationパラメータのDurationのフレームレートを計算して、1フレーム毎にAnimatedBuilderanimationが通知を受け取り、Alignをリビルドしています。

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
    
    省略
    
    _controller = AnimationController(
      duration: Duration(milliseconds: 500),
      vsync: this,
    );
    
    省略
    
    AnimatedBuilder(
          animation: animation,
          builder: (context, _) {
            return Align(
              alignment: animation.value,
              child: Container(
                width: 200,
                height: 200,
                color: Colors.purple[200],
              ),
            );
          },
        ),

✅まとめ

今回は、flutterでanimationを実装してみました。

移動するanimationは、
『アニメーションの進行具合』『始点と終点の位置』『Widgetに反映させるanimation』の
3つがあれば作成する事ができます。
今回紹介したWidget以外を追加する事で、
『曲線を描いたアニメーション』『回転するアニメーション』など、さまざまなアニメーションが実現可能になります。

今回はここまで 

Discussion