大多數 App 中都會有底部導航欄,通過底部導航欄切換實現不同頁面之間的切換。在Flutter 中提供了 BottomNavigationBar元件實現底部導航。本篇介紹通過 BottomNavigationBar和 IndexedStack構建最為常見的 App 頁面框架。
最終實現的結果如上圖所示,頂部共用一個導航欄,底部有四個圖示導航,點選對應的圖示跳轉到對應的頁面。
圖示準備
本次例程需要4個圖示,2種顏色,可以從 iconfont 中找到自己需要的圖示下載不同的顏色使用。然後在 pubspec.yaml 中的 assets 指定素材所在目錄。需要注意的是如果是 png 檔案直接指定整個目錄即可,但如果是 jpg 格式,則需要同時指定檔名及字尾。
BottomNavigationBar 簡介
BottomNavigationBar的建構函式如下:
BottomNavigationBar({
Key? key,
required this.items,
this.onTap,
this.currentIndex = 0,
this.elevation,
this.type,
Color? fixedColor,
this.backgroundColor,
this.iconSize = 24.0,
Color? selectedItemColor,
this.unselectedItemColor,
this.selectedIconTheme,
this.unselectedIconTheme,
this.selectedFontSize = 14.0,
this.unselectedFontSize = 12.0,
this.selectedLabelStyle,
this.unselectedLabelStyle,
this.showSelectedLabels,
this.showUnselectedLabels,
this.mouseCursor,
})
複製程式碼
其中常用的屬性為:
- items:及對應的頁面元件陣列
- currentIndex:預設顯示第幾個頁面
- type:元件型別,使用BottomNavigationBarType列舉,有 fixed 和 shifting 兩種。fixed 是圖示固定位置,而 shifting 的圖示點選後會有一個漂移效果,可以實際試一下,一般用fixed 比較多。
- onTap:點選後的事件,一般用這個更新狀態資料,以便更新頁面。
其他屬性用於控制樣式的,可以根據實際需要設定圖示大小,主題色,字型等引數。
構建專案頁面結構
首先,新建四個業務頁面,分別是 dynamic.dart,message.dart,category.dart 和 mine.dart,分別對應動態、訊息、分類瀏覽和個人中心四個頁面。目前這四個頁面很簡單,只是在頁面中間依次顯示“島上碼農”四個字。程式碼都是類似的,以 dynamic 為例:
import 'package:flutter/material.dart';
class DynamicPage extends StatelessWidget {
const DynamicPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('島'),
),
);
}
}
複製程式碼
注意的是這裡的 Scaffold 沒有 AppBar 了,這是因為在首頁已經有了,如果再有 AppBar 就會出現兩個。
其次,新建首頁,用於管理四個業務頁面,命名為 app.dart。app.dart 使用了 BottomNavigationBar 管理四個業務頁面的切換。
import 'package:flutter/material.dart';
import 'dynamic.dart';
import 'message.dart';
import 'category.dart';
import 'mine.dart';
class AppHomePage extends StatefulWidget {
AppHomePage({Key key}) : super(key: key);
@override
_AppHomePageState createState() => _AppHomePageState();
}
class _AppHomePageState extends State<AppHomePage> {
int _index = 0;
List<Widget> _homeWidgets = [
DynamicPage(),
MessagePage(),
CategoryPage(),
MinePage(),
];
void _onBottomNagigationBarTapped(index) {
setState(() {
_index = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('島上碼農'),
),
body: IndexedStack(
index: _index,
children: _homeWidgets,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _index,
onTap: _onBottomNagigationBarTapped,
items: [
_getBottomNavItem(
'動態', 'images/dynamic.png', 'images/dynamic-hover.png', 0),
_getBottomNavItem(
' 訊息', 'images/message.png', 'images/message-hover.png', 1),
_getBottomNavItem(
'分類瀏覽', 'images/category.png', 'images/category-hover.png', 2),
_getBottomNavItem(
'個人中心', 'images/mine.png', 'images/mine-hover.png', 3),
],
),
);
}
BottomNavigationBarItem _getBottomNavItem(
String title, String normalIcon, String pressedIcon, int index) {
return BottomNavigationBarItem(
icon: _index == index
? Image.asset(
pressedIcon,
width: 32,
height: 28,
)
: Image.asset(
normalIcon,
width: 32,
height: 28,
),
label: title,
);
}
}
複製程式碼
這裡關鍵的地方有兩個,一是使用的 IndexedStack,這是一個管理頁面顯示層級的容器。使用 index 屬性確定當前容器裡那個頁面在最頂上,容器裡的頁面通過 children 屬性設定,要求是一個 Widget 陣列。因此,邏輯就是當 BottomNavigationBar 中的圖示被點選後,對應點選事件會回撥 onTap屬性指定的方法,將當前的點選索引值傳遞迴調函式,因此可以利用這個方式控制 IndexedStack 的頁面層級切換。
最後,使用了狀態變數_index 儲存IndexedStatck當前顯示頁面的索引值,然後當 BottomNavigationBar的圖示點選事件發生後,在回撥函式中使用 setState 更新狀態變數_index 來重新整理當前介面。
簡化入口
main.dart 是入口檔案,應當做最基礎的配置和全域性初始化配置,而不應該有業務程式碼,因此可以簡化為從main 方法載入首頁即可。通過這種方式可以讓 main.dart 檔案即為簡潔。這也是在開發的時候需要注意的地方,將不相關的程式碼剝離,相關的程式碼聚合,即所謂的“高內聚,低耦合”原則。
import 'package:flutter/material.dart';
import 'app.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App 框架',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AppHomePage(),
);
}
}
複製程式碼
程式碼複用
寫程式碼的時候要注意複用,在這裡將構建 BottomNavigationBar 元素抽離出了一個構建方法_getBottomNavItem,從而提高程式碼的複用性和維護性,也可以避免 Flutter 的元件構建的 build 方法中出現過多的元素和巢狀,影響程式碼的可讀性。