前言
BottomBar就是底部導航,使用的是BottomNavigationBar這個元件
有兩種寫法(名字都是我為了方便解釋自己取名的) :1. 頁面抽換法 2. 頁面導航法
頁面抽換法
直接在 BottomNavigationBar 的 onTap 方法中處理導航
完整程式碼先附上
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter BottomNavigationBar Example',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0; // 當前選中的索引
// 頁面列表,每個選項對應一個頁面
final List<Widget> _pages = [
HomePage(),
SearchPage(),
ProfilePage(),
];
// 切換底部導航項目的回調函數
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("BottomNavigationBar Example"),
),
body: _pages[_selectedIndex], // 顯示當前選中的頁面
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex, // 設定當前選中的索引
onTap: _onItemTapped, // 處理點擊事件
selectedItemColor: Colors.blue, // 選中顏色
unselectedItemColor: Colors.grey, // 未選中顏色
backgroundColor: Colors.white,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(child: Text("Home Page"));
}
}
class SearchPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(child: Text("Search Page"));
}
}
class ProfilePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(child: Text("Profile Page"));
}
}
以下解釋
- _selectedIndex:當前選中的底部導航項目索引,用來確定顯示哪一頁。
- _pages:頁面列表,包含不同的頁面 widget(如 HomePage、SearchPage、ProfilePage)。每個頁面與 BottomNavigationBarItem 一一對應。
- _onItemTapped:當點擊 BottomNavigationBarItem 時觸發,會更新 _selectedIndex 並觸發重繪,從而切換頁面。
- BottomNavigationBar:
- currentIndex:當前選中的項目索引,通過 _selectedIndex 設置。
- onTap:回調函數,用於處理點擊事件。
- items:定義每個 BottomNavigationBarItem,包括圖標和標籤。
頁面導航法
使用回調 onChanged 來處理導航,且每次導航都是重新建構頁面
DhiWise是採用此種方式去寫BottomBar,有高擴充性和UI抽離的優點,但是還是有效能缺點和狀態不保留的缺點
先附上程式碼
以下是DhiWise原碼,我只有刪掉一些不相干的,還是會有點複雜,請見諒
以下程式碼中的 BottomNavigationBar 導航機制主要使用 CustomBottomBar 和 _buildBottomNavigation 函式。當用戶點擊 BottomNavigationBar 上的按鈕時,會透過 onChanged 回調觸發 getCurrentRoute 方法,進行頁面切換。
class WorkModeScreen extends StatefulWidget {
const WorkModeScreen({Key? key})
: super(
key: key,
);
@override
WorkModeScreenState createState() => WorkModeScreenState();
static Widget builder(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => WorkModeProvider(),
child: WorkModeScreen(),
);
}
}
// ignore_for_file: must_be_immutable
class WorkModeScreenState extends State<WorkModeScreen> {
final TaskService _taskService = TaskService();
List<TaskModel> _tasks = [];
GlobalKey<NavigatorState> navigatorKey = GlobalKey();
int _selectedIndex = 0;
@override
void initState() {
super.initState();
}
Future<void> _loadTasks() async {
final tasks = await _taskService.getTasks();
setState(() {
_tasks = tasks;
});
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
key: navigatorKey,
drawer: WorkModeMenuDrawerDraweritem(),
appBar: _buildAppBar(context),
body: SizedBox(
width: double.maxFinite,
child: SingleChildScrollView(
child: Container(
width: double.maxFinite,
padding: EdgeInsets.only(
left: 12.h,
top: 12.h,
right: 12.h,
),
child: Column(
children: [
_buildHighlightSection(context),
SizedBox(height: 12.h),
Container(
width: double.maxFinite,
padding: EdgeInsets.all(12.h),
decoration: BoxDecoration(
color: appTheme.teal50,
borderRadius: BorderRadiusStyle.roundedBorder10,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 4.h),
SizedBox(
width: double.maxFinite,
child: _buildRowcheckOne(
context,
checkOne: ImageConstant.imgCheck,
one: "lbl32".tr,
),
),
SizedBox(height: 12.h),
_buildTaskCard(context),
SizedBox(height: 12.h),
_buildTaskCard1(context),
SizedBox(height: 12.h),
_buildTaskCard2(context)
],
),
),
SizedBox(height: 28.h)
],
),
),
),
),
bottomNavigationBar: SizedBox(
width: double.maxFinite,
child: _buildBottomNavigation(context),
),
),
);
}
/// Section Widget
PreferredSizeWidget _buildAppBar(BuildContext context) {
return CustomAppBar(
leadingWidth: 48.h,
leading: Builder(
builder: (context) => AppbarLeadingImage(
imagePath: ImageConstant.imgMenu,
margin: EdgeInsets.only(
left: 24.h,
),
onTap: () {
Scaffold.of(context).openDrawer();
},
),
),
title: SizedBox(
width: double.maxFinite,
child: AppbarTitleButton(
margin: EdgeInsets.only(
left: 12.h,
),
onTap: () {
onTaptf(context);
},
),
),
actions: [
AppbarTrailingImage(
imagePath: ImageConstant.imgClockBlack900,
),
AppbarSubtitleFour(
text: "lbl_0_13".tr,
margin: EdgeInsets.only(
left: 6.h,
right: 12.h,
),
)
],
// styleType: Style.bgOutlineGray400,
);
}
/// Section Widget
Widget _buildBottomNavigation(BuildContext context) {
return SizedBox(
width: double.maxFinite,
child: CustomBottomBar(
onChanged: (BottomBarEnum type) {
Navigator.pushNamed(
navigatorKey.currentContext!, getCurrentRoute(type));
},
),
);
}
///Handling route based on bottom click actions
String getCurrentRoute(BottomBarEnum type) {
switch (type) {
case BottomBarEnum.Home:
return AppRoutes.workModeScreen;
case BottomBarEnum.Calendar:
return AppRoutes.meModeScreen;
case BottomBarEnum.Search:
return "/";
case BottomBarEnum.Plus:
return AppRoutes.workModeAddScreen;
default:
return "/";
}
}
/// Navigates to the meModeScreen when the action is triggered.
onTaptf(BuildContext context) {
NavigatorService.pushNamed(
AppRoutes.meModeScreen,
);
}
}
BottomBar導航機制解說
- BottomNavigationBar 的生成:
- CustomBottomBar 是一個客製化的 BottomNavigationBar,用於顯示底部導航按鈕。每個按鈕點擊時,都會觸發 onChanged 回調,並將當前的頁面類型 (BottomBarEnum) 傳遞給 _buildBottomNavigation。
- 底部導航欄位建構函式 _buildBottomNavigation:
- _buildBottomNavigation 函式負責生成底部導航欄。這裡通過 CustomBottomBar 的 onChanged 屬性,將 BottomBarEnum 類型的值傳遞給 getCurrentRoute 方法。
- 根據按鈕類型決定導航路徑 getCurrentRoute:
- getCurrentRoute 根據 BottomBarEnum 中的頁面類型決定導航的路由地址。
- 它接受 BottomBarEnum 作為輸入,並根據其值返回對應的路由名稱。
- BottomBarEnum.Home:返回 AppRoutes.workModeScreen 路由。
- BottomBarEnum.Calendar:返回 AppRoutes.meModeScreen 路由。
- BottomBarEnum.Search:返回 /(主頁)。
- BottomBarEnum.Plus:返回 AppRoutes.workModeAddScreen。
- 導航至指定路由 Navigator.pushNamed:
- Navigator.pushNamed 使用 navigatorKey 進行頁面導航。
- navigatorKey 是一個全域 GlobalKey,用來在當前應用的 Navigator 中進行路由導航。Navigator.pushNamed 接受 context 和頁面路徑,來導航到 getCurrentRoute 返回的頁面。
優缺點比較
寫法類型 | 優點 | 缺點 | 適用場景 |
直接在 onTap 處理導航 | 簡單清晰,代碼量少 一目了然,導航邏輯集中在一處效能較高,沒有多餘的回調和組件層級 – 適合不頻繁改動的頁面切換,減少不必要的重繪 | 高耦合,導航和 UI 顯示綁定在一起,不利於測試和擴展 不易重用,增加代碼重複度對小型應用來說,響應快速、體驗流暢 – 簡單清晰的結構降低導航延遲,但缺乏自定義彈性 | 適合小型應用或頁面邏輯簡單的應用 |
使用自定義 BottomNavigationBar | 低耦合,導航和 UI 顯示分離 擴展性強,便於樣式和功能的變化 更適合大型項目和重用性使用者體驗靈活,便於在導航間加入動畫效果、過渡樣式等 支持更動態的頁面內容,有助於提升頁面交互體驗 | 代碼量增加,需要自定義組件 學習成本較高,初學者理解回調和封裝可能有些難度稍增加效能開銷,取決於頁面層級和回調次數 若結構設計不佳,可能導致不必要的重繪 | 適合大型應用,頁面結構多樣或需要重用 |