🐕‍🦺

【Flutter】Material Design3のNavigationBarを本気で学ぶ

2022/08/21に公開

この記事について

本気で学ぶシリーズ第3弾です。公式ドキュメントを参考に、初学者から中級者に向けてわかりやすく解説出来ればと思います。指摘事項や助言などは歓迎です。もしございましたら、Twitterもしくは記事コメントまでお願い致します。

NavigationBarの基礎

※この記事はBottomNavigationBarの知識を前提にしています。詳しくはまずはこちらをご覧ください。
https://zenn.dev/urasan/articles/5bb85a54fb23fb

NavigationBarはMaterial Design3に対応したBottomNavigationBarです。

NavigationBarクラスはBottomNavigationBarと同様に、ScaffoldのbottomNavigationBarプロパティに配置します。

NavigationBarはBottomNavigationBarとほとんど同じですが、以下の大きな違いがあります。

・NavigationBarはMaterial Design3にdefaultで対応している。(それに応じてかなりの数の見た目変更プロパティや要素が消えた)
・BottomNavigationBarはStatefulWidgetを継承しているが、NavigationBarはStatelessWidgetを継承している。
・BottomNavigationBarのtypeプロパティが無いので、items(destinations)を4個以上設定しても自動でshiftingする事は無い。

https://m3.material.io/components/navigation-bar/overview

NavigationBarのプロパティ

NavigationBarウィジェットのプロパティはBottomNavigationBarと、多少の相違点があります。

・itemsという概念ではなく、destinationsという概念の導入
・見た目を手動で変えるプロパティの大幅減少(Material Design3の考えに基づいて)

相違点を中心とし、これに関連するプロパティを解説していきます。

prop名 説明
animationDuration 画面遷移時のアニメーション持続時間です。
backgroundColor NavigationBarの背景色を決定します。
destinations itemsという概念から置き換わった物です。itemsとは違いbackgroundColorプロパティはありません。
selectedIndex 現在地のページのインデックス番号を(指定)表しています。
labelBehavior ラベルを①常に表示②常に非表示③選択されているラベルだけ表示という3つのふるまいを定義出来ます。NavigationDestinationLabelBehaviorというEnumを置いて指定します。指定しない場合はNavigationBarThemeDataが参照され、それもnullの場合はalwaysShowが適用されます。
onDestinationSelected destinationsが選択された時(変化した時)に呼び出されます。ほぼ間違いなくselectedIndexに関連した処理が書かれる事で、ページ遷移する事になるかと思います。型はValueChangedなので、結果的にはBottomNavBarで言うonTapと同じ処理になるかと思います。

個人的な感想ですが、BottomNavigationBarよりもプロパティは少なくて、覚える事が少なくシンプルです。BottomNavigationBarから新しい機能が追加されたというよりは、名称が変わり、色々なプロパティが減った。と考える方が適切でしょう。

全部盛りサンプル

デザインはともかくとして、上記に述べたプロパティを出来る限り全て使ったNavigationBarです。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return const MaterialApp(home: NavigationExample());
  }
}

class NavigationExample extends StatefulWidget {
  const NavigationExample({Key? key}) : super(key: key);

  
  State<NavigationExample> createState() => _NavigationExampleState();
}

class _NavigationExampleState extends State<NavigationExample> {
  var _currentPageIndex = 0;

  final _pages = <Widget>[
    Container(
      color: Colors.red,
      alignment: Alignment.center,
      child: const Text('Page 1'),
    ),
    Container(
      color: Colors.green,
      alignment: Alignment.center,
      child: const Text('Page 2'),
    ),
    Container(
      color: Colors.blue,
      alignment: Alignment.center,
      child: const Text('Page 3'),
    ),
    Container(
      color: Colors.yellow,
      alignment: Alignment.center,
      child: const Text('Page 4'),
    ),
    Container(
      color: Colors.orange,
      alignment: Alignment.center,
      child: const Text('Page 5'),
    ),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: NavigationBar(
        onDestinationSelected: (int index) {
          setState(() {
            _currentPageIndex = index;
          });
        },
        selectedIndex: _currentPageIndex,
        // 下のプロパティで背景色を設定できます。
        // backgroundColor: Colors.black,
        animationDuration: const Duration(seconds: 10),
        elevation: 10,
        height: 100,
	labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
        destinations: const <Widget>[
          NavigationDestination(
              icon: Icon(Icons.explore), label: 'ver A', tooltip: "tooltip1"),
          NavigationDestination(
              icon: Icon(Icons.commute), label: 'ver B', tooltip: "tooltip2"),
          NavigationDestination(
            icon: Icon(Icons.bookmark_border),
            label: 'ver C',
            tooltip: "tooltip3",
            selectedIcon: Icon(Icons.bookmark),
          ),
          NavigationDestination(
              icon: Icon(Icons.circle), label: 'ver D', tooltip: "tooltip4"),
          NavigationDestination(
              icon: Icon(Icons.train), label: 'ver E', tooltip: "tooltip5"),
        ],
      ),
      body: _pages[_currentPageIndex],
    );
  }
}

Tips

よくある質問としてまとめます。要望やアイデアなどがあれば、ぜひご連絡ください。


できます。具体的には、ScaffordのbottomNavigationBarプロパティに、NavigationBarThemeクラスを配置して、見た目をカスタマイズすることで可能です。具体的な方法は以下の動画を参考にすることをお勧めします。非常にわかりやすいです。

ただし、Material design3の大きな特徴は、「2と見た目が変わった事」に加えて「テーマの統一とその容易さ」です。MaterialコンポーネントがThemeに合わせてくれるという利点とその思想が3の特徴なので、見た目だけ作ることを良いか悪いかは...何とも言えません。

https://www.youtube.com/watch?v=2emB2VFrRnA
引用:Johannes Milke channel: Flutter Tutorial - NEW Material You Navigation Bar | The New Way [2021] Flutter Navigation Bar


スワイプジェスチャー(横スクロール)で遷移したい。⇒

ScaffoldのbodyにPageViewを指定し、プロパティにコントローラーを登録、そのコントローラーを利用しこの機能を実装出来ます。BottomNavigationBarの記事とほとんど同じです。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return const MaterialApp(home: NavigationExample());
  }
}

class NavigationExample extends StatefulWidget {
  const NavigationExample({Key? key}) : super(key: key);

  
  State<NavigationExample> createState() => _NavigationExampleState();
}

class _NavigationExampleState extends State<NavigationExample> {
  var _currentPageIndex = 0;
  final _pageViewController = PageController();

  final _pages = <Widget>[
    Container(
      color: Colors.red,
      alignment: Alignment.center,
      child: const Text('Page 1'),
    ),
    Container(
      color: Colors.green,
      alignment: Alignment.center,
      child: const Text('Page 2'),
    ),
    Container(
      color: Colors.blue,
      alignment: Alignment.center,
      child: const Text('Page 3'),
    ),
    Container(
      color: Colors.yellow,
      alignment: Alignment.center,
      child: const Text('Page 4'),
    ),
    Container(
      color: Colors.orange,
      alignment: Alignment.center,
      child: const Text('Page 5'),
    ),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: NavigationBar(
        onDestinationSelected: (int index) {
          setState(() {
            _pageViewController.animateToPage(index,
                duration: const Duration(milliseconds: 200),
                curve: Curves.easeOut);
          });
        },
        selectedIndex: _currentPageIndex,
        // 下のプロパティで背景色を設定できます。
        // backgroundColor: Colors.black,
        animationDuration: const Duration(seconds: 10),
        elevation: 10,
        height: 100,
        labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
        destinations: const <Widget>[
          NavigationDestination(
              icon: Icon(Icons.explore), label: 'ver A', tooltip: "tooltip1"),
          NavigationDestination(
              icon: Icon(Icons.commute), label: 'ver B', tooltip: "tooltip2"),
          NavigationDestination(
            icon: Icon(Icons.bookmark_border),
            label: 'ver C',
            tooltip: "tooltip3",
            selectedIcon: Icon(Icons.bookmark),
          ),
          NavigationDestination(
              icon: Icon(Icons.circle), label: 'ver D', tooltip: "tooltip4"),
          NavigationDestination(
              icon: Icon(Icons.train), label: 'ver E', tooltip: "tooltip5"),
        ],
      ),
      body: PageView(
        controller: _pageViewController,
        children: _pages,
        onPageChanged: (index) {
          setState(() {
            _currentPageIndex = index;
          });
        },
      ),
    );
  }
}


BottomNavigationBarとNavigationBarどっちがいいですか?⇒

上記に述べているように、基本的な違いは、①StatelessかStatefulか②Material Design3に対応しているか。です。これらに応じて選択するのが良いでしょう。

ただ、悩んでいるという事は、①と②についてまだ上手く認識出来ていないという事だと思いますので、個人的にはBottomNavigationBarを使う事をお勧めします。
理由は、ネットで検索した時に拾える情報が多いからです。

最後に

ありがとうございました。

感想やご要望などはTwitterまで連絡いただければ幸いです。
https://twitter.com/urasan_edu

Discussion