👻

【Flutter】BottomNavigationBarでスクロール以外の遷移する際のアニメーションをさせてみる

2024/05/19に公開

やりたいこと

いわゆるフッターで画面遷移を行う際にパッと画面が切り替わるのが味気ないのでアニメーションをさせました。

調べるとPageViewにしてpositionをずらして横方向のスクロールをさせて遷移する。という方法やよく出てきたのですが、その場合一番左のメニューから一番右のメニューに遷移した場合、他の画面が表示されてしまうので微妙と思っています。

他の遷移アニメーションがないかを探索してみました。

パッと切り替わるパターン

PageViewでpositionをずらして横方向のスクロールをして遷移させるパターン

こんな感じでできた

フェード

縦方向

横方向

公式のpackageであるanimations
PageTransitionSwitcherSharedAxisTransitionFadeThroughTransitionの組み合わせで遷移アニメーションを作りました。

https://pub.dev/packages/animations

一旦、コード一式を貼り付けます。

final menus = [
  (icon: Icons.settings, label: 'RED', color: Colors.red),
  (icon: Icons.account_circle, label: 'BLUE', color: Colors.blue),
  (icon: Icons.show_chart, label: 'YELLOW', color: Colors.yellow),
  (icon: Icons.calendar_month, label: 'GREEN', color: Colors.green),
];

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

  @override
  State<StatefulWidget> createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  final views = [
    ColoredView(key: GlobalKey(), menus[0].color, menus[0].label),
    ColoredView(key: GlobalKey(), menus[1].color, menus[1].label),
    ColoredView(key: GlobalKey(), menus[2].color, menus[2].label),
    ColoredView(key: GlobalKey(), menus[3].color, menus[3].label),
  ];

  var selectedMenuIndex = 0;

  @override
  Widget build(context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('BottomNavBarApp'),
        backgroundColor: Theme.of(context).colorScheme.primaryContainer,
      ),
      body: PageTransitionSwitcher(
        transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
          return FadeThroughTransition(
            animation: primaryAnimation,
            secondaryAnimation: secondaryAnimation,
            child: child,
          );
        },
        child: views[selectedMenuIndex],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: selectedMenuIndex,
        items: menus
            .map((e) =>
                BottomNavigationBarItem(icon: Icon(e.icon), label: e.label))
            .toList(),
        onTap: (value) => setState(() => selectedMenuIndex = value),
        type: BottomNavigationBarType.fixed,
      ),
    );
  }
}

ScaffoldbodyPageTransitionSwitcherをセットし、transitionBuilderFadeThroughTransitionをセットすればフェードアニメーションで遷移します。
また、SharedAxisTransitionをセットすれば、縦方向や横方向のアニメーションで遷移します。

躓いたこと

この記事を書くために分かりやすく背景を単色にして同じレイアウトのViewを以下の様に作成していました。

class ColoredView extends StatelessWidget {
  final Color color;
  final String label;

  const ColoredView(this.color, this.label, {super.key});

  @override
  Widget build(BuildContext context) {
    return ColoredBox(
      color: color,
      child: Center(
        child: Text(label),
      ),
    );
  }
}

  final views = [
    ColoredView(menus[0].color, menus[0].label),
    ColoredView(menus[1].color, menus[1].label),
    ColoredView(menus[2].color, menus[2].label),
    ColoredView(menus[3].color, menus[3].label),
  ];

ただ、上記のコードだとPageTransitionSwitcherを使ってもアニメーションされませんでした。
どうやら同じクラスで中身だけ違うようにすると、PageTransitionSwitcherは同じ画面として扱ってしまうようでした。
(例えばSettingView(設定画面)やAccountView(アカウント画面)のように別のクラスを配列にセットする場合はkeyをセットせずともアニメーションが発生しました。)

そのため、ColoredViewにKeyをセットすることで、ちゃんとアニメーションをするようになりました。

  final views = [
    ColoredView(key: GlobalKey(), menus[0].color, menus[0].label),
    ColoredView(key: GlobalKey(), menus[1].color, menus[1].label),
    ColoredView(key: GlobalKey(), menus[2].color, menus[2].label),
    ColoredView(key: GlobalKey(), menus[3].color, menus[3].label),
  ];

Discussion