底部導覽列 BottomNavigationBar


Scaffold 有個 bottomNavigationBar 特性,從名稱上來看,似乎是接受 Flutter 的 BottomNavigationBar,不過其實它接受的是 Widget

其實到目前也看過很多這種特性了,在 Flutter 的在慣例上,許多特性的名稱並不是指可以接受哪個型態,而是告訴你,這邊放的 UI 元件,通常會是做什麼用,例如,bottomNavigationBar 特性告訴你的是,這邊通常會做為底部導覽列。

當然,你想放別的東西,也是沒關係的,例如,故意放個 TabBar,像是把〈分頁工具列 TabBar〉中分頁放到底部:

import 'package:flutter/material.dart';

void main() => runApp(
  MaterialApp(
    home: Home()
  )
);

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _HomeState();
}

class _HomeState extends State<Home>  with SingleTickerProviderStateMixin{
  TabController tabController;

  @override
  void initState() {
    tabController = new TabController(length: 3, vsync: this);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TabBarView(
        controller: tabController,
        children: [
          Book(
            imgSrc: 'https://openhome.cc/Gossip/images/ACL059300.jpg',
            name: 'Java SE 14 技術手冊',
          ),
          Book(
            imgSrc: 'https://openhome.cc/Gossip/images/ACL054400.jpg',
            name: 'Python 3.7 技術手冊',
          ),
          Book(
            imgSrc: 'https://openhome.cc/Gossip/images/AEL022800.jpg',
            name: 'JavaScript 技術手冊',
          ),
        ],
      ),
      // TabBar 指定給 bottomNavigationBar
      bottomNavigationBar: Container(
        color: Colors.blue,
        child: TabBar(
          tabs: [
            Tab(text: 'Java'),
            Tab(text: 'Python'),
            Tab(text: 'JavaScript'),
          ],
          controller: tabController,
        ),
      ),
    );
  }
}

class Book extends StatelessWidget {
  final String imgSrc;
  final String name;

  Book({this.imgSrc, this.name});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Center(
          child: Image.network(imgSrc),
        ),
        Text(name)
      ],
    );
  }
}

執行的結果畫面會是:

底部導覽列 BottomNavigationBar

就 UI 的建立與組合上,Flutter 算是蠻有彈性的,只要你瞭解 Flutter 的架構,基本上想一切從頭建立自己的元件也可以,不見得要用它的標準元件。

當然,現成的標準元件可以馬上拿來用,是蠻方便的,元件的特性名稱雖然並不指它可以接受什麼型態,然而通常暗示著,會有個類似名稱的標準元件可以使用,例如 bottomNavigationBar 就暗示著有個 BottomNavigationBar 可以用。

只不過,每個元件都有各自設計的方式,雖然 BottomNavigationBar 感覺有點像是 TabBar,不過元件間的組合方式並不一樣,或者從另一個角度來看,選擇哪個要視你的應用程式撰寫需求而定,如果合適的話,要將 BottomNavigationBar 放到 AppBar 中作為子元件之一,然後放到畫面上方去,基本上也是做得到的。

這邊還是將 BottomNavigationBar 指定給 bottomNavigationBar 就可以了,來看個例子:

import 'package:flutter/material.dart';

void main() => runApp(
  MaterialApp(
    home: Home()
  )
);

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  final books = [
    Book(
      imgSrc: 'https://openhome.cc/Gossip/images/ACL059300.jpg',
      name: 'Java SE 14 技術手冊',
    ),
    Book(
      imgSrc: 'https://openhome.cc/Gossip/images/ACL054400.jpg',
      name: 'Python 3.7 技術手冊',
    ),
    Book(
      imgSrc: 'https://openhome.cc/Gossip/images/AEL022800.jpg',
      name: 'JavaScript 技術手冊',
    )
  ];

  var bookIdx;

  @override
  void initState() {
    bookIdx = 0;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 依 bookIdx 決定 body 要顯示的元件
      body: books[bookIdx],

      // 使用 BottomNavigationBar
      bottomNavigationBar: BottomNavigationBar(
        backgroundColor: Colors.blue,

        // 導覽列項目
        items: [
          // 每個項目是 BottomNavigationBarItem 實例
          // 必須設定 icon
          BottomNavigationBarItem(title: Text('Java'), icon: Icon(Icons.local_cafe)),
          BottomNavigationBarItem(title: Text('Python'), icon: Icon(Icons.local_grocery_store)),
          BottomNavigationBarItem(title: Text('JavaScript'), icon: Icon(Icons.local_pizza)),
        ],

        // 選中的項目
        currentIndex: bookIdx,
        selectedFontSize: 16,
        selectedItemColor: Colors.white,

        // 點選項目時改變 bookIdx
        onTap: (idx) => setState(() {
          bookIdx = idx;
        }),
      ),
    );
  }
}

class Book extends StatelessWidget {
  同前一範例…略
}

基本上需要注意的地方,都在註解中說明了,BottomNavigationBar 需要指定圖示,對使用者比較有友善吧!來看一下執行的結果:

底部導覽列 BottomNavigationBar

可以看到,BottomNavigationBar 本身沒有動畫的效果,這是跟 TabBar 不一樣的地方,就 Material Design 的設計來看,動畫藉由視覺上的效果,讓使用者知道畫面之間的前後關係,這可能會是你選擇 TabBarBottomNavigationBar 的依據之一,BottomNavigationBar 需要指定圖示也是考量之一。

另一方面,就過去 GUI 設計的經驗來說,通常 Tab 分頁會是同一個功能頁面下各個不同細項設定之用,至於導覽嘛!每個頁面彼此之間通常是獨立的吧!當然了,選擇在個人,主要還是看需求而定。