🎨

flutter_animateでお手軽アニメーション実装!✨

2024/12/06に公開

アニメーションを実装することで、アプリの印象ってガラリと変わりますよね!🎨

Flutterを採用してるアプリだとPoCやレビューに向けての短期間開発は結構あるかと思います。
限られた開発期間の中でも、手軽にアニメーションを実装してアプリの品質を上げられたら理想的です!

ただAnimationControllerやTweenなど組み合わせて実装するとなると、特にFlutter経験が浅い場合は理解までに意外と時間がかかるかと思います。

そういう時は flutter_animate を使ってお手軽アニメーション実装しましょう!

flutter_animateとは?🤔

シンプルで直感的!いろんな種類のアニメーションが実装できるパッケージです。
https://pub.dev/packages/flutter_animate

Flutter Favoritesにも選定されていて、公式YouTube動画のPackage Of the Weekでも紹介されています。
https://youtu.be/JSqUZFkRLr8?si=dsykpyH7fFLLPSB1

基本的な実装🌱

公式でもサンプルコードが載っていますが、実際に触れてみたのでこの記事でもいくつか紹介します!

基本的にアニメーションさせたいウィジェットに対してAnimateで囲むことでアニメーションを適用させることができます。

Animate(
  effects: [FadeEffect(), ScaleEffect()],
  child: Text("Hello World!"),
)

また、より簡単に実装できるよう、flutter_animateをインストールするとすべてのウィジェットにanimateという拡張関数が利用できるようになります。
こっちのがより直感的なので以降のコードもこちらで実装しています!

rotate 🔄

Icon(Icons.public)
    .animate(
      onPlay: (controller) => controller.repeat(),
    )
    .rotate();

このコードではIcons.publicを繰り返し回転させるようアニメーションさせています。
animate関数はいくつかプロパティを設定でき、ここではonPlayで繰り返しアニメーションするよう実装しています。

shake 📳

Stack(
  children: [
    const Icon(Icons.notifications_outlined)
        .animate(
          onPlay: (controller) => controller.repeat(),
        )
        .shake(duration: 500.ms, delay: 1.seconds),
    const Positioned(
      right: 2,
      top: 2,
      child: DecoratedBox(
        decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle),
        child: Padding(
          padding: EdgeInsets.all(1.0),
          child: DecoratedBox(
            decoration: BoxDecoration(color: Colors.red, shape: BoxShape.circle),
            child: SizedBox(width: 8, height: 8),
          ),
        ),
      ),
    ),
  ],
);

Icons.notifications_outlinedを揺らすようアニメーションさせてます。
各アニメーションエフェクトの関数ではduration, delayが用意されており、
durationではアニメーション再生時間を、delayではアニメーション開始までの遅延時間を設定できます。
ここでは1秒間置きに、0.5秒間アニメーションさせています。

fadeIn/fadeOut 🌗

const Text('Fade In')
    .animate(
      onPlay: (controller) => controller.repeat(),
    )
    .fadeIn(duration: 2.seconds)
    .then(delay: 1.seconds)
    .fadeOut(duration: 2.seconds);

フェードインとフェードアウトを1秒置きに繰り返しアニメーションさせています。
このようにthen関数を使って複数アニメーションを組み合わせることも可能です。

scale 🔍

const Text("Scale")
    .animate(
      onPlay: (controller) => controller.repeat(),
    )
    .scale(
      begin: const Offset(1.0, 1.0),
      end: const Offset(1.5, 1.5),
      duration: 2.seconds,
      delay: 1.seconds,
      curve: Curves.easeInOut,
    )
    .then(delay: 1.seconds)
    .scale(
      begin: const Offset(1.5, 1.5),
      end: const Offset(1.0, 1.0),
      duration: 2.seconds,
      curve: Curves.easeInOut,
    );

テキストを拡大するようアニメーションさせています。
beginでアニメーション開始時位置、endでアニメーション終了時位置を設定できます。
curveではアニメーション進行具合を設定しています。Curves.easeInOutを設定することで、アニメーションの開始と終了はゆっくりと、中盤では素早く再生させています。

操作イベントによるアニメーション実装☝️

ここまでのアニメーションは自動再生していました。
操作イベントでもアニメーションを制御できます!

タップ時アニメーション👆

var isFavorite = false;

void toggleFavorite() {
  setState(() {
    isFavorite = !isFavorite;
  });
}

GestureDetector(
  onTap: toggleFavorite,
  child: Icon(
    isFavorite ? Icons.star : Icons.star_outline,
  ),
)
    .animate(
      target: isFavorite ? 1.0 : 0.0,
      autoPlay: false,
    )
    .scale(
      begin: const Offset(1.0, 1.0),
      end: const Offset(1.3, 1.3),
      curve: Curves.easeInOut,
    )
    .then()
    .scale(
      begin: const Offset(1.3, 1.3),
      end: const Offset(1.0, 1.0),
      curve: Curves.easeInOut,
    );

autoPlayをfalseで設定することで自動アニメーションをオフにしています。
アイコンタップ時にisFavoriteの状態を変更し、targetの値を切り替えることでアニメーションさせています。
1.0ではbeginendの順でアニメーションされ、0.0ではendbeginの順でアニメーションされることを利用してタップ時に再生、逆再生をするようにしています。

スクロール時再生📜

import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';

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

  
  State<SampleScroll> createState() => _SampleScrollState();
}

class _SampleScrollState extends State<SampleScroll> {
  late ScrollController controller;

  
  void initState() {
    super.initState();
    controller = ScrollController();
  }

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: ListView.builder(
          controller: controller,
          itemCount: 50,
          itemBuilder: (context, index) {
            return const ListTile(
              leading: CircleAvatar(child: FlutterLogo()),
              title: Text('SubTitle', style: TextStyle(fontSize: 24)),
              subtitle: Text('Description', style: TextStyle(fontSize: 18)),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => controller.animateTo(
          controller.position.minScrollExtent,
          duration: const Duration(milliseconds: 500),
          curve: Curves.easeInOut,
        ),
        child: const Icon(Icons.arrow_upward),
      )
          .animate(
            autoPlay: false,
            adapter: ScrollAdapter(controller, end: 150),
          )
          .scale()
          .animate(
            autoPlay: false,
            adapter: ScrollAdapter(controller, begin: 3000),
          )
          .fadeOut(),
    );
  }
}

ここではスクロールに応じてFloatingActionButtonをアニメーションさせています。
ScrollAdapterScrollControllerを設定することで簡単にスクロールに合わせてアニメーションさせることができます。
beginendを設定することでスクロール位置に応じてアニメーションの開始と終了を制御できます。

開発時のTips&アクセシビリティ🛠️

開発時はホットリロードでアニメーションが再生されるよう、restartOnHotReloadをオンに設定しておくと便利です!

Animate.restartOnHotReload = true;

また少し話が逸れますが、今後アクセシビリティを高めていきたい場合はアニメーション再生時間も気をつけなければなりません!
どれほどのアクセシビリティ基準に対応するかにもよりますが、例えばWCAGの達成基準2.1では繰り返し再生するアニメーションは5秒以内に停止することとしています。
https://waic.jp/translations/WCAG21/Techniques/general/G152

この場合、下記のようにonPlayで5秒後には停止するようanimateを実装しておくと良いかと思います。

const Icon(Icons.notifications_outlined).animate(
  onPlay: (controller) {
    controller.repeat();
    Future.delayed(5.seconds, () {
      controller.stop();
    });
  },
).shake(duration: 500.ms, delay: 1.seconds),

まとめ🎉

flutter_animateを使用することで、シンプルで効果的なアニメーション実装ができました!
最後に上記で紹介したアニメーションを使って簡単な画面を実装したサンプルリポジトリもアップロードしましたので、少しでもご参考になれば幸いです。

https://github.com/shofucchi/sample_flutter_animate

アニメーションを駆使してユーザーに親しみやすいアプリを提供できるよう、これからもFlutterライフを楽しんでいければと思います!💪

Discussion