Flutter自作アニメーションの作り方


Flutter自作アニメーションの作り方

この記事でやること

  • 0% から 100% に文字を変化させるアニメーションを作成する

HeroWidgetなど公式のWidgetを使うとFlutterでは簡単に単純なアニメーションを作成することができるが、
自分で細かく調整したい場合はAnimationControllerを使った方がやりやすかったので紹介。

面倒くさいのでHeroWidgetのようなアニメーションを付けてくれるWidgetをまず先に探して、
どうしても無かった場合はこの方法でどうにかする、というスタンスでいいと思う。

事前知識

  • Flutter Stateful Widget の使い方
    • setStateでWidgetを作り直すなど

全体の流れ

  • テスト画面作成
  • Ticker作成
  • Controller作成
  • 文字列に代入

テスト画面を作る

文字を中央に表示するだけ。

今回はmain.dartを書き換えているが、適宜アニメーションを付けたいWidgetに置き換える。
statefull widgetで作る必要があることに注意。

以下サンプルコード

import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: Text('custom animation test'),
        ),
        body: Center(
          child: Text(
            //TODO animate text from 0 to 100%
            '0%',
            style: TextStyle(
              fontSize: 40.0,
            ),
          ),
        ),
      ),
    );
  }
}

アニメーション作成

Ticker作成

まずはTickerを作る。

Tickerは0から1の値を変動していく。
Tickerの値が変化するたびに新しいフレームを作成していき、繋げてみると動いているように見えるということ。

これは単純にアニメーションを付けたいWidgetにMixinをつけることで作成できる。

例えば作るアニメーションが1つの場合は

with SingleTickerProviderStateMixin

をアニメーションをしたいWidgetに付ける。

サンプル


class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    ....

Controller作成

アニメーションの開始するタイミングや、変動する範囲を決められる。

今回は0から100までの値を3秒間かけて変化するコントローラーを作る。
コントローラーはWidget作成時に最初に完成している必要があるのでinitState()内で作成する。

(flutterのwidgetが実行される順番は initState >>> build >>> dispose)

サンプル

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  AnimationController controller;

  @override
  void initState() {
    super.initState();

    controller = AnimationController(
      vsync: this, // このWidgetのTickerと同期するように指定
      duration: Duration(seconds: 3),
      lowerBound: 0.0, 
      upperBound: 100.0, 
    );
  }

  @override
  Widget build(BuildContext context) {
    ...

アニメーションが終わったらコントローラーを削除する。
これを行わないとWidgetが削除されてもコントローラーの計算が永遠と行われることになる。

Widget削除時に実行されるdispose内に記述する。


  @override
  void dispose() {
    super.dispose();

    controller.dispose();
  }

Controllerを動かす

値が0から100まで3秒間かけて変化するControllerを

initState内で開始させるコマンドcontroller.forward();を書き、

Widget作成時に実行されているか確認してみる。

サンプル


  @override
  void initState() {
    super.initState();

    controller = AnimationController(
      vsync: this, // このWidgetのTickerと同期するように指定
      duration: Duration(seconds: 3),
      lowerBound: 0.0,
      upperBound: 100.0,
    );

    controller.forward(); // アニメーション開始

    controller.addListener(() {
      print(controller.value); // コンソールで値を確認
    });
  }

実行結果

Hot Restartすると


Restarted application in 956ms.
flutter: 0.0
flutter: 0.0
flutter: 12.396533333333332
flutter: 12.9521
flutter: 14.0632
flutter: 14.618733333333333
flutter: 15.174299999999999
flutter: 15.174299999999999
flutter: 15.174299999999999

...

flutter: 94.598
flutter: 95.15356666666666
flutter: 95.70913333333334
flutter: 96.26466666666667
flutter: 96.82023333333333
flutter: 97.3758
flutter: 97.93133333333334
flutter: 98.4869
flutter: 99.0425
flutter: 99.598
flutter: 100.0

のように値が0から100まで変化していることが確認できる。

Contoroller.value をテキストに代入

Contoroller.valueが変化するたびにWidgetを作り直したい。

そのためにcontroller.addListener内にsetStateを書く。


    controller.addListener(() {
      print(controller.value);
      setState(() {});
    });

main.dart内で0%を表示していたテキストをcontroller.value.toInt()に置き換える。

          child: Text(
            //TODO animate text from 0 tp 100%
            '${controller.value.toInt()}%',
            style: TextStyle(
              fontSize: 40.0,
            ),
          ),

Hot Restartすると

めんどう