docs

TOP(About this memo)) > 一覧(Flutter) > アニメーション

Flutter のアニメーションの作成

Ticker, TickerProvider

Animation

AnimationController(Animationサブクラス)

Animatable

import 'dart:math';

import 'package:flutter/material.dart';

class TweenTest extends StatefulWidget {
  const TweenTest({super.key});

  @override
  State<TweenTest> createState() => _TweenTestState();
}

class _TweenTestState extends State<TweenTest>
    with SingleTickerProviderStateMixin {
  final _scaleTween = Tween<double>(begin: 1.0, end: 0.01);
  final _rotateTween = Tween<double>(begin: 0, end: 360);
  final _duration = 3000;

  late AnimationController _animationController;
  late Animation _scaleAnimation;
  late Animation _rotateAnimation;
  late Animation _sequenseAnimation;

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

    _animationController = AnimationController(
      duration: Duration(milliseconds: _duration),
      vsync: this,
    );

    _scaleAnimation = _scaleTween.animate(_animationController)
      ..addListener(() {
        setState(() {});
      });

    _rotateAnimation = _rotateTween
        .chain(CurveTween(curve: Curves.easeIn))
        .animate(_animationController)
      ..addListener(() {
        setState(() {});
      });

    _sequenseAnimation = TweenSequence<double>(
      <TweenSequenceItem<double>>[
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 0.0, end: 300.0)
              .chain(CurveTween(curve: Curves.bounceOut)),
          weight: 40.0,
        ),
        TweenSequenceItem<double>(
          tween: ConstantTween<double>(300.0),
          weight: 40.0,
        ),
        TweenSequenceItem<double>(
          tween: Tween<double>(begin: 300.0, end: 0.0)
              .chain(CurveTween(curve: Curves.easeOut)),
          weight: 20.0,
        ),
      ],
    ).animate(_animationController);

    _animationController.repeat();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Transform.translate(
          offset: Offset(0.0, _sequenseAnimation.value),
          child: const FlutterLogo(
            size: 100.0,
          ),
        ),
        Transform.translate(
          offset: Offset(
              0.0,
              MediaQuery.of(context).size.width *
                  0.5 *
                  _animationController.value),
          child: Transform.rotate(
            angle: pi / 180 * _rotateAnimation.value,
            child: Transform.scale(
              scale: _scaleAnimation.value,
              child: const SizedBox(
                height: 100.0,
                width: 100.0,
                child: ColoredBox(
                  color: Colors.black,
                ),
              ),
            ),
          ),
        )
      ],
    );
  }

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

内部処理

abstract class Animatable<T> {
    //...
    T transform(double t);

    T evaluate(Animation<double> animation) => transform(animation.value);

    Animation<T> animate(Animation<double> parent) {
        return _AnimatedEvaluation<T>(parent, this);
    }

    Animatable<T> chain(Animatable<double> parent) {
        return _ChainedEvaluation<T>(parent, this);
    }
}
//...
class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
    _AnimatedEvaluation(this.parent, this._evaluatable);
    // ...
    @override
    T get value => _evaluatable.evaluate(parent);
    // ...
}
class Tween<T extends Object?> extends Animatable<T> {
  //...
  @protected
  T lerp(double t) {
    return (begin as dynamic) + ((end as dynamic) - (begin as dynamic)) * t as T;
  }

  @override
  T transform(double t) {
    if (t == 0.0) {
      return begin as T;
    }
    if (t == 1.0) {
      return end as T;
    }
    return lerp(t);
  }
  //...
}

アニメーションに関連するウィジェット

AnimatiedBuilder

AnimatedWidget

アニメーションオブジェクトを直接渡すウィジェット

コントローラやアニメーションオブジェクトを利用せずアニメーションを実現する

その他

フレーム単位でsetStateを行う際の注意

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
      home: Scaffold(
    body: Center(
      child: TextFieldTest(),
    ),
  )));
}

class TextFieldTest extends StatefulWidget {
  const TextFieldTest({super.key});

  @override
  State<TextFieldTest> createState() => _TextFieldTestState();
}

class _TextFieldTestState extends State<TextFieldTest>
    with SingleTickerProviderStateMixin {
  @override
  void initState() {
    super.initState();

    final ticker = createTicker((_) {
      setState(() {});
    });
    ticker.start();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: TextField(
          controller: TextEditingController(),
        ),
      ),
    );
  }
}