Flutter實(shí)戰(zhàn) Scaffold、TabBar、底部導(dǎo)航

2021-03-08 10:41 更新

Material 組件庫提供了豐富多樣的組件,本節(jié)介紹一些常用的組件,其余的讀者可以自行查看文檔或 Flutter Gallery 中 Material 組件部分的示例。

Flutter Gallery 是 Flutter 官方提供的 Flutter Demo,源碼位于 flutter 源碼中的 examples 目錄下,筆者強(qiáng)烈建議用戶將 Flutter Gallery 示例跑起來,它是一個(gè)很全面的 Flutter 示例應(yīng)用,是非常好的參考 Demo,也是筆者學(xué)習(xí) Flutter 的第一手資料。

#5.6.1 Scaffold

一個(gè)完整的路由頁可能會(huì)包含導(dǎo)航欄、抽屜菜單(Drawer)以及底部Tab導(dǎo)航菜單等。如果每個(gè)路由頁面都需要開發(fā)者自己手動(dòng)去實(shí)現(xiàn)這些,這會(huì)是一件非常麻煩且無聊的事。幸運(yùn)的是,F(xiàn)lutter Material組件庫提供了一些現(xiàn)成的組件來減少我們的開發(fā)任務(wù)。Scaffold是一個(gè)路由頁的骨架,我們使用它可以很容易地拼裝出一個(gè)完整的頁面。

#示例

我們實(shí)現(xiàn)一個(gè)頁面,它包含:

  1. 一個(gè)導(dǎo)航欄
  2. 導(dǎo)航欄右邊有一個(gè)分享按鈕
  3. 有一個(gè)抽屜菜單
  4. 有一個(gè)底部導(dǎo)航
  5. 右下角有一個(gè)懸浮的動(dòng)作按鈕

最終效果如圖5-18、圖5-19所示:

圖5-18 圖5-19

實(shí)現(xiàn)代碼如下:

  1. class ScaffoldRoute extends StatefulWidget {
  2. @override
  3. _ScaffoldRouteState createState() => _ScaffoldRouteState();
  4. }
  5. class _ScaffoldRouteState extends State<ScaffoldRoute> {
  6. int _selectedIndex = 1;
  7. @override
  8. Widget build(BuildContext context) {
  9. return Scaffold(
  10. appBar: AppBar( //導(dǎo)航欄
  11. title: Text("App Name"),
  12. actions: <Widget>[ //導(dǎo)航欄右側(cè)菜單
  13. IconButton(icon: Icon(Icons.share), onPressed: () {}),
  14. ],
  15. ),
  16. drawer: new MyDrawer(), //抽屜
  17. bottomNavigationBar: BottomNavigationBar( // 底部導(dǎo)航
  18. items: <BottomNavigationBarItem>[
  19. BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
  20. BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
  21. BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
  22. ],
  23. currentIndex: _selectedIndex,
  24. fixedColor: Colors.blue,
  25. onTap: _onItemTapped,
  26. ),
  27. floatingActionButton: FloatingActionButton( //懸浮按鈕
  28. child: Icon(Icons.add),
  29. onPressed:_onAdd
  30. ),
  31. );
  32. }
  33. void _onItemTapped(int index) {
  34. setState(() {
  35. _selectedIndex = index;
  36. });
  37. }
  38. void _onAdd(){
  39. }
  40. }

上面代碼中我們用到了如下組件:

組件名稱 解釋
AppBar 一個(gè)導(dǎo)航欄骨架
MyDrawer 抽屜菜單
BottomNavigationBar 底部導(dǎo)航欄
FloatingActionButton 漂浮按鈕

下面我們來分別介紹一下它們。

#5.6.2 AppBar

AppBar是一個(gè) Material 風(fēng)格的導(dǎo)航欄,通過它可以設(shè)置導(dǎo)航欄標(biāo)題、導(dǎo)航欄菜單、導(dǎo)航欄底部的Tab標(biāo)題等。下面我們看看 AppBar 的定義:

  1. AppBar({
  2. Key key,
  3. this.leading, //導(dǎo)航欄最左側(cè)Widget,常見為抽屜菜單按鈕或返回按鈕。
  4. this.automaticallyImplyLeading = true, //如果leading為null,是否自動(dòng)實(shí)現(xiàn)默認(rèn)的leading按鈕
  5. this.title,// 頁面標(biāo)題
  6. this.actions, // 導(dǎo)航欄右側(cè)菜單
  7. this.bottom, // 導(dǎo)航欄底部菜單,通常為Tab按鈕組
  8. this.elevation = 4.0, // 導(dǎo)航欄陰影
  9. this.centerTitle, //標(biāo)題是否居中
  10. this.backgroundColor,
  11. ... //其它屬性見源碼注釋
  12. })

如果給Scaffold添加了抽屜菜單,默認(rèn)情況下Scaffold會(huì)自動(dòng)將AppBarleading設(shè)置為菜單按鈕(如上面截圖所示),點(diǎn)擊它便可打開抽屜菜單。如果我們想自定義菜單圖標(biāo),可以手動(dòng)來設(shè)置leading,如:

  1. Scaffold(
  2. appBar: AppBar(
  3. title: Text("App Name"),
  4. leading: Builder(builder: (context) {
  5. return IconButton(
  6. icon: Icon(Icons.dashboard, color: Colors.white), //自定義圖標(biāo)
  7. onPressed: () {
  8. // 打開抽屜菜單
  9. Scaffold.of(context).openDrawer();
  10. },
  11. );
  12. }),
  13. ...
  14. )

代碼運(yùn)行效果如圖5-20所示:

可以看到左側(cè)菜單已經(jīng)替換成功。

代碼中打開抽屜菜單的方法在ScaffoldState中,通過Scaffold.of(context)可以獲取父級(jí)最近的Scaffold 組件的State對象。

#TabBar

下面我們通過“bottom”屬性來添加一個(gè)導(dǎo)航欄底部 Tab 按鈕組,將要實(shí)現(xiàn)的效果如圖5-21所示:

圖5-21

Material 組件庫中提供了一個(gè)TabBar組件,它可以快速生成Tab菜單,下面是上圖對應(yīng)的源碼:

  1. class _ScaffoldRouteState extends State<ScaffoldRoute>
  2. with SingleTickerProviderStateMixin {
  3. TabController _tabController; //需要定義一個(gè)Controller
  4. List tabs = ["新聞", "歷史", "圖片"];
  5. @override
  6. void initState() {
  7. super.initState();
  8. // 創(chuàng)建Controller
  9. _tabController = TabController(length: tabs.length, vsync: this);
  10. }
  11. @override
  12. Widget build(BuildContext context) {
  13. return Scaffold(
  14. appBar: AppBar(
  15. ... //省略無關(guān)代碼
  16. bottom: TabBar( //生成Tab菜單
  17. controller: _tabController,
  18. tabs: tabs.map((e) => Tab(text: e)).toList()
  19. ),
  20. ),
  21. ... //省略無關(guān)代碼
  22. }

上面代碼首先創(chuàng)建了一個(gè)TabController ,它是用于控制/監(jiān)聽Tab菜單切換的。接下來通過 TabBar 生成了一個(gè)底部菜單欄,TabBartabs屬性接受一個(gè) Widget 數(shù)組,表示每一個(gè) Tab 子菜單,我們可以自定義,也可以像示例中一樣直接使用Tab 組件,它是 Material 組件庫提供的 Material 風(fēng)格的 Tab 菜單。

Tab組件有三個(gè)可選參數(shù),除了可以指定文字外,還可以指定Tab菜單圖標(biāo),或者直接自定義組件樣式。Tab組件定義如下:

  1. Tab({
  2. Key key,
  3. this.text, // 菜單文本
  4. this.icon, // 菜單圖標(biāo)
  5. this.child, // 自定義組件樣式
  6. })

開發(fā)者可以根據(jù)實(shí)際需求來定制。

#TabBarView

通過TabBar我們只能生成一個(gè)靜態(tài)的菜單,真正的 Tab 頁還沒有實(shí)現(xiàn)。由于Tab菜單和 Tab 頁的切換需要同步,我們需要通過TabController去監(jiān)聽 Tab 菜單的切換去切換 Tab 頁,代碼如:

  1. _tabController.addListener((){
  2. switch(_tabController.index){
  3. case 1: ...;
  4. case 2: ... ;
  5. }
  6. });

如果我們 Tab 頁可以滑動(dòng)切換的話,還需要在滑動(dòng)過程中更新 TabBar 指示器的偏移!顯然,要手動(dòng)處理這些是很麻煩的,為此,Material 庫提供了一個(gè)TabBarView組件,通過它不僅可以輕松的實(shí)現(xiàn) Tab 頁,而且可以非常容易的配合 TabBar 來實(shí)現(xiàn)同步切換和滑動(dòng)狀態(tài)同步,示例如下:

  1. Scaffold(
  2. appBar: AppBar(
  3. ... //省略無關(guān)代碼
  4. bottom: TabBar(
  5. controller: _tabController,
  6. tabs: tabs.map((e) => Tab(text: e)).toList()),
  7. ),
  8. drawer: new MyDrawer(),
  9. body: TabBarView(
  10. controller: _tabController,
  11. children: tabs.map((e) { //創(chuàng)建3個(gè)Tab頁
  12. return Container(
  13. alignment: Alignment.center,
  14. child: Text(e, textScaleFactor: 5),
  15. );
  16. }).toList(),
  17. ),
  18. ... // 省略無關(guān)代碼
  19. )

運(yùn)行后效果如圖5-22所示:

圖5-22

現(xiàn)在,無論是點(diǎn)擊導(dǎo)航欄 Tab 菜單還是在頁面上左右滑動(dòng),Tab 頁面都會(huì)切換,并且 Tab 菜單的狀態(tài)和 Tab 頁面始終保持同步!那它們是如何實(shí)現(xiàn)同步的呢?細(xì)心的讀者可能已經(jīng)發(fā)現(xiàn),上例中TabBarTabBarViewcontroller是同一個(gè)!正是如此,TabBarTabBarView正是通過同一個(gè)controller來實(shí)現(xiàn)菜單切換和滑動(dòng)狀態(tài)同步的,有關(guān)TabController的詳細(xì)信息,我們不在本書做過多介紹,使用時(shí)讀者直接查看 SDK 即可。

另外,Material 組件庫也提供了一個(gè)PageView 組件,它和TabBarView功能相似,讀者可以自行了解一下。

#5.6.3 抽屜菜單Drawer

ScaffolddrawerendDrawer屬性可以分別接受一個(gè) Widget 來作為頁面的左、右抽屜菜單。如果開發(fā)者提供了抽屜菜單,那么當(dāng)用戶手指從屏幕左(或右)側(cè)向里滑動(dòng)時(shí)便可打開抽屜菜單。本節(jié)開始部分的示例中實(shí)現(xiàn)了一個(gè)左抽屜菜單MyDrawer,它的源碼如下:

  1. class MyDrawer extends StatelessWidget {
  2. const MyDrawer({
  3. Key key,
  4. }) : super(key: key);
  5. @override
  6. Widget build(BuildContext context) {
  7. return Drawer(
  8. child: MediaQuery.removePadding(
  9. context: context,
  10. //移除抽屜菜單頂部默認(rèn)留白
  11. removeTop: true,
  12. child: Column(
  13. crossAxisAlignment: CrossAxisAlignment.start,
  14. children: <Widget>[
  15. Padding(
  16. padding: const EdgeInsets.only(top: 38.0),
  17. child: Row(
  18. children: <Widget>[
  19. Padding(
  20. padding: const EdgeInsets.symmetric(horizontal: 16.0),
  21. child: ClipOval(
  22. child: Image.asset(
  23. "imgs/avatar.png",
  24. width: 80,
  25. ),
  26. ),
  27. ),
  28. Text(
  29. "Wendux",
  30. style: TextStyle(fontWeight: FontWeight.bold),
  31. )
  32. ],
  33. ),
  34. ),
  35. Expanded(
  36. child: ListView(
  37. children: <Widget>[
  38. ListTile(
  39. leading: const Icon(Icons.add),
  40. title: const Text('Add account'),
  41. ),
  42. ListTile(
  43. leading: const Icon(Icons.settings),
  44. title: const Text('Manage accounts'),
  45. ),
  46. ],
  47. ),
  48. ),
  49. ],
  50. ),
  51. ),
  52. );
  53. }
  54. }

抽屜菜單通常將Drawer組件作為根節(jié)點(diǎn),它實(shí)現(xiàn)了 Material 風(fēng)格的菜單面板,MediaQuery.removePadding可以移除 Drawer 默認(rèn)的一些留白(比如 Drawer 默認(rèn)頂部會(huì)留和手機(jī)狀態(tài)欄等高的留白),讀者可以嘗試傳遞不同的參數(shù)來看看實(shí)際效果。抽屜菜單頁由頂部和底部組成,頂部由用戶頭像和昵稱組成,底部是一個(gè)菜單列表,用 ListView 實(shí)現(xiàn),關(guān)于 ListView 我們將在后面“可滾動(dòng)組件”一節(jié)詳細(xì)介紹。

#5.6.4 FloatingActionButton

FloatingActionButton是 Material 設(shè)計(jì)規(guī)范中的一種特殊 Button,通常懸浮在頁面的某一個(gè)位置作為某種常用動(dòng)作的快捷入口,如本節(jié)示例中頁面右下角的"?"號(hào)按鈕。我們可以通過ScaffoldfloatingActionButton屬性來設(shè)置一個(gè)FloatingActionButton,同時(shí)通過floatingActionButtonLocation屬性來指定其在頁面中懸浮的位置,這個(gè)比較簡單,不再贅述。

#5.6.5 底部Tab導(dǎo)航欄

我們可以通過ScaffoldbottomNavigationBar屬性來設(shè)置底部導(dǎo)航,如本節(jié)開始示例所示,我們通過 Material 組件庫提供的BottomNavigationBarBottomNavigationBarItem兩種組件來實(shí)現(xiàn) Material 風(fēng)格的底部導(dǎo)航欄??梢钥吹缴厦娴膶?shí)現(xiàn)代碼非常簡單,所以不再贅述,但是如果我們想實(shí)現(xiàn)如圖5-23所示效果的底部導(dǎo)航欄應(yīng)該怎么做呢?

圖5-23

Material組件庫中提供了一個(gè)BottomAppBar 組件,它可以和FloatingActionButton配合實(shí)現(xiàn)這種“打洞”效果,源碼如下:

  1. bottomNavigationBar: BottomAppBar(
  2. color: Colors.white,
  3. shape: CircularNotchedRectangle(), // 底部導(dǎo)航欄打一個(gè)圓形的洞
  4. child: Row(
  5. children: [
  6. IconButton(icon: Icon(Icons.home)),
  7. SizedBox(), //中間位置空出
  8. IconButton(icon: Icon(Icons.business)),
  9. ],
  10. mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部導(dǎo)航欄橫向空間
  11. ),
  12. )

可以看到,上面代碼中沒有控制打洞位置的屬性,實(shí)際上,打洞的位置取決于FloatingActionButton的位置,上面FloatingActionButton的位置為:

  1. floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,

所以打洞位置在底部導(dǎo)航欄的正中間。

BottomAppBarshape屬性決定洞的外形,CircularNotchedRectangle實(shí)現(xiàn)了一個(gè)圓形的外形,我們也可以自定義外形,比如,F(xiàn)lutter Gallery 示例中就有一個(gè)“鉆石”形狀的示例,讀者感興趣可以自行查看。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)