在認識 BottomNavigationBar
的同時,往往都會看到 BottomAppBar
,不少文件會說,如果 BottomNavigationBar
不能滿足你,想自訂個底部工具列,可以使用 BottomAppBar
,這麼說某些程度上也沒錯啦!
例如,來模仿一下 BottomNavigationBar
,在 bottom_nav_bar.dart 中實作個 BottomNavnBar
:
import 'package:flutter/material.dart';
class BottomNavBar extends StatefulWidget {
final items;
final Function onTap;
BottomNavBar({this.items, this.onTap});
@override
State<StatefulWidget> createState() => _BottomNavBar();
}
class _BottomNavBar extends State<BottomNavBar> {
int selectedIdx = 0;
@override
Widget build(BuildContext context) {
final barItems = List<Widget>();
for(var i = 0; i < widget.items.length; i++) {
barItems.add(Column(
// 依元件決定最小高度
mainAxisSize: MainAxisSize.min,
// 從底部排列
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
// 依選中與否,從父裔中取得佈景主題的顏色
color: selectedIdx == i ? Theme.of(context).accentIconTheme.color : Colors.black,
icon: widget.items[i].icon,
onPressed: () {
setState(() {
selectedIdx = i;
widget.onTap(i);
});
},
),
DefaultTextStyle(
style: TextStyle(
// 依選中與否,從父裔中取得佈景主題的顏色
color: selectedIdx == i ? Theme.of(context).accentIconTheme.color : Colors.black,
),
child: widget.items[i].title,
)
],
));
}
return BottomAppBar(
color: Theme.of(context).accentColor,
child: Row(
// 平均分配空間
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: barItems,
)
);
}
}
class BottomNavBarItem {
Widget title;
Icon icon;
BottomNavBarItem({this.title, this.icon});
}
這麼一來,就可以將〈底部導覽列 BottomNavigationBar〉中的 BottomNavigationBar
用 BottomNavBar
取代:
import 'package:flutter/material.dart';
import 'bottom_nav_bar.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();
}
void page(idx) {
setState(() {
bookIdx = idx;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: books[bookIdx],
bottomNavigationBar: BottomNavBar(
items: [
BottomNavBarItem(title: Text('Java'), icon: Icon(Icons.local_cafe)),
BottomNavBarItem(title: Text('Python'), icon: Icon(Icons.local_grocery_store)),
BottomNavBarItem(title: Text('JavaScript'), icon: Icon(Icons.local_pizza)),
],
onTap: page,
),
);
}
}
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)
],
);
}
}
執行結果是差不多的:
只不過,幹嘛還包個 BottomAppBar
?直接將 _BottomNavBar
中 build
裡的 Row
指定給 Scaffold
的 bottomNavigationBar
不就好了?沒錯!如果只是要自訂底部工具列,根本就不需要 BottomAppBar
,方才只是純綷想玩,模仿一下 BottomNavigationBar
罷了。
其實 API 文件中寫到,BottomAppBar
主要是為了搭配 Scaffold
的 floatingActionButton
特性,如果 floatingActionButton
蓋到 BottomAppBar
,你可以提供一個缺口的繪製效果。例如,在 _BottomNavBar
的 build
做點修改:
import 'package:flutter/material.dart';
...
class _BottomNavBar extends State<BottomNavBar> {
int selectedIdx = 0;
@override
Widget build(BuildContext context) {
...略
return BottomAppBar(
color: Theme.of(context).accentColor,
// 設定缺口形狀
shape: CircularNotchedRectangle(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: barItems,
)
);
}
}
...略
然後,設定一下 Scaffold
的 floatingActionButton
與 floatingActionButtonLocation
:
import 'package:flutter/material.dart';
import 'bottom_nav_bar.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 技術手冊',
),
];
var bookIdx;
...略
@override
Widget build(BuildContext context) {
return Scaffold(
// 設定 FloatingActionButton
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
)),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
body: books[bookIdx],
bottomNavigationBar: BottomNavBar(
items: [
BottomNavBarItem(title: Text('Java'), icon: Icon(Icons.local_cafe)),
BottomNavBarItem(title: Text('Python'), icon: Icon(Icons.local_grocery_store)),
],
onTap: page,
),
);
}
}
class Book extends StatelessWidget {
...略
}
就會出現以下的效果了:
要注意的是,缺口效果是畫出來的,顏色是由 BottomAppBar
的 color
決定,也就是說,若想要有缺口效果,自訂 BottomAppBar
時,就不能設定其 child
元件的顏色,例如底下可以呈現缺口:
import 'package:flutter/material.dart';
import 'bottom_nav_bar.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
)),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
color: Colors.blue,
shape: CircularNotchedRectangle(),
child: Container(
height: kBottomNavigationBarHeight,
)
),
)
)
);
執行結果如下:
然而,設定了 Container
的 color
後,就不會有缺口了:
import 'package:flutter/material.dart';
import 'bottom_nav_bar.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
)),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
color: Colors.blue,
shape: CircularNotchedRectangle(),
child: Container(
color: Colors.green, // 設定了顏色
height: kBottomNavigationBarHeight,
)
),
)
)
);
這是因為缺口畫完後,就又被 Container
的顏色繪製給覆蓋過去了:
因此正確來說,BottomAppBar
是給你自訂有缺口的工具列,而且是專門為了搭配 Scaffold
的 floatingActionButton
特性。
那麼可不可以提供按鈕處凸起而不是缺口呢?BottomAppBar
的 shape
接受的 NotchedShape
,顧名思義,它應該就是用來做缺口用的,不過,NotchedShape
主要是提供一個 Path
:
abstract class NotchedShape {
const NotchedShape();
Path getOuterPath(Rect host, Rect guest);
}
也就是說,提供一個形狀的路徑資訊,CircularNotchedRectangle
提供的應就是個與矩形計算後,具有缺口的矩形路徑資訊,然後再用來繪圖,因此如果有心研究一下 BottomAppBar
是怎麼利用 NotchedShape
繪圖的話,是可以做出按鈕處凸起的效果,網路上有一些看來不錯的專案,提供這類的效果,搜尋一下「convex bottom bar」應該就能找到。