2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【flutter】TweenSequenceとuseAnimationControllerを用いて複雑なアニメーションを実装する

Last updated at Posted at 2024-03-27

はじめに

Flutterでは、アニメーションを実装するためにTweenやAnimationControllerなどの便利なクラスが提供されています。今回は、TweenSequenceとuseAnimationControllerを組み合わせて、通知アニメーションを実装してみました。
notification.gif

開発環境

  • Flutter: 3.14.0
  • Dart: 3.2.0

パッケージのインストール

flutter_hookshooks_riverpodというパッケージを利用します。

  flutter_hooks: ^0.20.5
  hooks_riverpod: ^2.5.1

useAnimationControllerとは

useAnimationControllerは、FlutterHooksパッケージに含まれるフックの一つです。これを使用することで、アニメーションの開始、停止、逆再生など簡単に操作することができます。

useAnimationControllerの使い方

useAnimationControllerの使い方を簡単に説明します。

以下のようにしてuseAnimationControllerを定義することができます。durationにはアニメーション全体の所要時間を設定します。

final animationController = useAnimationController(duration: const Duration(seconds: 2));

今回は以下2つのメソッドを使用しました。

メソッド 用途
animationController.forward(); アニメーションの開始
animationController.reset() アニメーションのリセット

TweenSequenceとは

TweenSequenceは、Tweenのリストを使用して、時間にそってアニメーションの値(tween.value)を変化させることができるクラスです。今回実装する通知アニメーションのように、複数の段階で変化するアニメーションを実装する場合に活躍します。

TweenSequenceの使い方

TweenSequenceTweenSequenceItemのリストを持ちます。各TweenSequenceItemにはtweenweightを指定します。

  • tween (begine: 始点, end: 終点)
  • weight (そのTweenがアニメーションの進行に占める時間の割合)

例えば、以下のコードではアニメーション全体の時間が5秒なので
5秒の40% = 2秒, 20% = 1秒より以下の表のように変化します。

経過時間 tween.value(一次関数的に変化)
0~2秒 0→100に変化
2~3秒 100で一定
3~5秒 100→0に変化
final animationController =
        useAnimationController(duration: const Duration(seconds: 5));
    final tween = TweenSequence([
      TweenSequenceItem(
        tween: Tween(
          begin: 0,
          end: 100,
        ),
        weight: 40,
      ),
      TweenSequenceItem(
        tween: ConstantTween(100),
        weight: 20,
      ),
      TweenSequenceItem(
        tween: Tween(
          begin: 100,
          end: 0,
        ),
        weight: 40,
      ),
    ]).animate(animationController);

Widgetの実装

簡単な通知のWidgetを作成しました。

Widget buildNotificationWidget(context) {
    return SafeArea(
      child: Material(
        type: MaterialType.transparency,
        child: Container(
          height: 100,
          width: MediaQuery.of(context).size.width,
          color: Colors.green,
          padding: const EdgeInsets.only(top: 50),
          child: const Center(
            child: Text('お知らせ'),
          ),
        ),
      ),
    );
  }
}

AnimationBuilderを使用して、アニメーションが更新されるたびにウィジェットそ再構築し、アニメーションの値に応じてウィジェットを配置します。

return AnimatedBuilder(
  animation: animationController,
  builder: (context, _) {
    return Stack(
      children: [
        Positioned(
          top: tween.value,
          child: buildNotificationWidget(context),
        ),
        // 他のウィジェットの配置
      ],
    );
  },
);

コード全文

これらを組み合わせることで通知アニメーションのような複雑なアニメーションを構築することが可能になります。以下のコードではボタンが押された時に通知アニメーションが開始するようになっています。

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class NotificationWidget extends HookConsumerWidget {
  const NotificationWidget({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // ステータスバーの高さ + ウィジェットの高さ + 上マージン + 影を考慮
    final movedPosition = -MediaQuery.of(context).padding.top;
    final initialPosition = -(MediaQuery.of(context).padding.top + 52 + 8);
    final animationController =
        useAnimationController(duration: const Duration(seconds: 4));
    final tween = TweenSequence([
      TweenSequenceItem(
        tween: Tween(
          begin: initialPosition,
          end: movedPosition,
        ),
        weight: 0.5,
      ),
      TweenSequenceItem(
        tween: ConstantTween(movedPosition),
        weight: 5,
      ),
      TweenSequenceItem(
        tween: Tween(
          begin: movedPosition,
          end: initialPosition,
        ),
        weight: 0.5,
      ),
    ]).animate(animationController);

    animationController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        animationController.reset();
      }
    });

    return AnimatedBuilder(
      animation: animationController,
      builder: (context, _) {
        return Positioned(
          top: tween.value,
          child: buildNotificationWidget(context),
        );
      },
    );
  }

  Widget buildNotificationWidget(context) {
    return SafeArea(
      child: Material(
        type: MaterialType.transparency,
        child: Container(
          height: 100,
          width: MediaQuery.of(context).size.width,
          color: Colors.green, // 後で直す
        ),
      ),
    );
  }
}

最後に

TweenSequenceとuseAnimationControllerを使えばどんな複雑なアニメーションでも作れる気がしてきてますね。ワクワクしました。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?