我們在flutter中可以使用Navigator.push或者Navigator.pushNamed方法來向Navigator中新增不同的頁面,從而達到頁面調整的目的。
一般情況下這樣已經足夠了,但是有時候我們有多個Navigator的情況下,上面的使用方式就不夠用了。比如我們有一個主頁面app的Navigator,然後裡面有一個匹配好友的功能,這個功能有多個頁面,因為匹配好友功能的多個頁面實際上是一個完整的流程,所以這些頁面需要被放在一個子Navigator中,並和主Navigator區分開。
那麼應該如何處理呢?
主Navigator是我們app的一些主要介面,這裡我們有三個介面,分別是主home介面,一個setting設定介面和好友匹配介面。
其中好友匹配介面包含了三個子介面,這三個子介面將會用到子路由。
先來看下主路由,主路由的情況跟普通的路由沒啥區別,這裡我們首先定義和home和setting匹配的兩個widget:HomePage和SettingsPage:
class HomePage extends StatelessWidget {
const HomePage({
super.key,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(context),
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
SizedBox(
width: 250,
height: 250,
child: Center(
child: Icon(
Icons.home,
size: 175,
color: Colors.blue,
),
),
),
SizedBox(height: 32),
Text(
'跳轉到好友匹配頁面',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).pushNamed(routeFriendMatch);
},
child: const Icon(Icons.add),
),
);
}
HomePage很簡單,它包含了一個floatingActionButton,當點選它的時候會呼叫 Navigator.pushNamed方法進行路由切換。
然後是SettingsPage,它是一個簡單的Column:
class SettingsPage extends StatelessWidget {
const SettingsPage({
super.key,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(),
body: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(8, (index) {
return ListTile(
title: Text('設定項$index'),
);
}),
),
),
);
}
最後一個頁面是FriendMatchFlow,這個頁面比較複雜,我們在下一個再進行講解。
然後我們為主路由在onGenerateRoute方法中進行定義:
void main() {
runApp(
MaterialApp(
onGenerateRoute: (settings) {
late Widget page;
if (settings.name == routeHome) {
page = const HomePage();
} else if (settings.name == routeSettings) {
page = const SettingsPage();
} else if (settings.name == routeFriendMatch) {
page = const FriendMatchFlow(
setupPageRoute: routeFriendMatchPage,
);
}
return MaterialPageRoute<dynamic>(
builder: (context) {
return page;
},
settings: settings,
);
},
debugShowCheckedModeBanner: false,
),
);
}
主路由很簡單,跟普通的路由沒有太多的區別。
接下來是構建子路由的步驟。在主路由中,如果路由的名稱是routeFriendMatch,那麼就會跳轉到FriendMatchFlow。
而這個flow頁面實際上是由幾個子頁面組成的:選擇好友頁面,等待頁面,匹配頁面和匹配完畢頁面。
具體的頁面程式碼這裡就不寫了,我們主要來講一下子路由的使用。
對於FriendMatchFlow來說,它本身是一個Navigator,所以我們的build方法是這樣的:
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _isExitDesired,
child: Scaffold(
appBar: _buildFlowAppBar(),
body: Navigator(
key: _navigatorKey,
initialRoute: widget.setupPageRoute,
onGenerateRoute: _onGenerateRoute,
),
),
);
}
因為他需要根據使用者的不同點選來進行內部路由的切換,所以需要儲存對當前子Navigator的應用,所以這裡FriendMatchFlow是一個StatefulWidget,並且上面的_navigatorKey是一個GlobalKey物件,以提供對子Navigator的參照:
final _navigatorKey = GlobalKey<NavigatorState>();
這裡的_onGenerateRoute方法,跟主路由也是很類似的,主要定義的是子路由中頁面的跳轉:
Route _onGenerateRoute(RouteSettings settings) {
late Widget page;
switch (settings.name) {
case routeFriendMatchPage:
page = WaitingPage(
message: '匹配附近的好友...',
onWaitComplete: _onDiscoveryComplete,
);
break;
case routeFriendSelectPage:
page = SelectFriendPage(
onFriendSelected: _onFriendSelected,
);
break;
case routeFriendConnectingPage:
page = WaitingPage(
message: '匹配中...',
onWaitComplete: _onConnectionEstablished,
);
break;
case routeFriendFinishedPage:
page = FinishedPage(
onFinishPressed: _exitSetup,
);
break;
}
這裡的on***Selected是VoidCallback回撥,用來進行路由的切換:
void _onDiscoveryComplete() {
_navigatorKey.currentState!.pushNamed(routeFriendSelectPage);
}
void _onFriendSelected(String deviceId) {
_navigatorKey.currentState!.pushNamed(routeFriendConnectingPage);
}
void _onConnectionEstablished() {
_navigatorKey.currentState!.pushNamed(routeFriendFinishedPage);
}
可以看到上面的路由切換實際上是在子路由上切換,跟父路由無關。
如果想要直接從子路由跳出到父路由該怎麼處理呢?很簡單,呼叫Navigator.of的pop方法即可:
void _exitSetup() {
Navigator.of(context).pop();
}
這裡的context預設是全域性的context,所以會導致主路由的跳轉變化。
以上的程式碼執行結果如下:
雖然上面的例子看起來複雜,但是大家只要記住了不同的路由使用不同的Navigator範圍進行跳轉就行了。
本文的例子:https://github.com/ddean2009/learn-flutter.git