參考官方文檔:https://docs.flutter.dev/cookbook/navigation
一、切換頁面的方式
- 直接切換,類似Android中的布局替換,因為flutter中都是組件,也就是組件直接替換。(不推薦)
- 路由跳轉頁面
- 基本路由 + 傳參數(一般)
- Navigator.push() 或者Navigator.of(context).push()跳轉和傳參數 MaterialPageRoute(builder: (context) => const SecondRoute(title:"你好flutter"))
- Navigator.pop() 或者Navigator.of(context).pop() 返回頁面 類似Android中的onBackPressed()方法
- 命名路由 + 傳參數 (推薦)
- 普通命名路由直接傳參數與接收 (常用 比較簡單和自由)
- 需要在MaterialApp下的routes進行注冊頁面
- Navigator.pushNamed(context,"路由名稱","參數") 進行跳轉和傳參數
- 接收參數 ModalRoute.of(context)!.settings.arguments
- 指定onGenerateRoute提取參數 (可以實現通用性,若想查看請在文章最下方查看)
- 不需要在routes中注冊路由
- 可以指定接收某個路由跳轉的參數做處理
- 雖然還是通過Navigator.pushNamed(context,"路由名稱","參數") 進行跳轉和傳參數但經過onGenerateRoute中處理
本質還是通過MaterialPageRoute(builder: (context) =>PassArgumentsScreen(title,message)構造函數在傳參數,只不過可以統一管理,頁面清晰
- 普通命名路由直接傳參數與接收 (常用 比較簡單和自由)
- 替換路由
- 普通路由
- 假設A,B,C三個頁面 若是A->B->C,最后C想返回到A頁面,就得在B->C中B頁面跳轉方法中使用這個函數Navigator.of(context).pushReplacement("/C",""參數"")
- 命名路由
- 假設A,B,C三個頁面 若是A->B->C,最后C想返回到A頁面,就得在B->C中B頁面跳轉方法中使用這個函數Navigator.of(context).pushReplacementNamed("/C",""參數"")
- 普通路由
- 跳轉到根目錄
- 普通路由
- 假設A,B,C,D四個頁面 若是A->B->C->D,最后D想返回到A頁面可以在B,C頁面中使用替換路由方式。
- 還有一種就是使用Navigator.of(context).pushAndRemoveUntil("首頁路由",條件)
//下面的HomeScreen()是我的首頁,但其實不是首頁也沒關系,任意一個頁面都是可以的 //因為這段代碼的含義是之道條件不滿足就會一直刪除先前加載的頁面,直到滿足條件,那么其實不是首頁也是可以的, //只是普遍用在返回首頁上。 Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder:(context)=> HomeScreen()), (route) => route ==null //這里也可以使用 ModalRoute.withName('/')但前提是你注冊了根路由'/' );
- 命名路由
- 假設A,B,C,D四個頁面 若是A->B->C->D,最后D想返回到A頁面可以在B,C頁面中使用替換路由方式。
- 還有一種就是使用Navigator.of(context).pushNamedAndRemoveUntil("首頁路由",條件)
Navigator.of(context).pushNamedAndRemoveUntil( MaterialPageRoute(builder:(context)=> HomeScreen()), ModalRoute.withName('/') );
- 普通路由
- 基本路由 + 傳參數(一般)
下面這段資料來自與網絡 也就是路由的跳轉的API更多可以參考下面
Navigator 繼承自 StatefulWidget,它有很多相關靜態函數,可以幫我們達到頁面跳轉和數據交互的功能:
- push 將設置的router信息推送到Navigator上,實現頁面跳轉。
- of 主要是獲取 Navigator最近實例的好狀態。
- pop 導航到新頁面,或者返回到上個頁面。
- canPop 判斷是否可以導航到新頁面
- maybePop 可能會導航到新頁面
- popAndPushNamed 指定一個路由路徑,並導航到新頁面。
- popUntil 反復執行pop 直到該函數的參數predicate返回true為止。
- pushAndRemoveUntil 將給定路由推送到Navigator,刪除先前的路由,直到該函數的參數predicate返回true為止。
- pushNamed 將命名路由推送到Navigator。
- pushNamedAndRemoveUntil 將命名路由推送到Navigator,刪除先前的路由,直到該函數的參數predicate返回true為止。
- pushReplacement 路由替換。
- pushReplacementNamed 這個也是替換路由操作。推送一個命名路由到Navigator,新路由完成動畫之后處理上一個路由。
- removeRoute 從Navigator中刪除路由,同時執行Route.dispose操作。
- removeRouteBelow 從Navigator中刪除路由,同時執行Route.dispose操作,要替換的路由是傳入參數anchorRouter里面的路由。
- replace 將Navigator中的路由替換成一個新路由。
- replaceRouteBelow 將Navigator中的路由替換成一個新路由,要替換的路由是是傳入參數anchorRouter里面的路由。
二、舉例說明:直接切換
import 'package:flutter/material.dart';
import 'package:myflutterapp/pages/tabs/category_page.dart';
import 'package:myflutterapp/pages/tabs/home_page.dart';
import 'package:myflutterapp/pages/tabs/my_page.dart';
import 'package:myflutterapp/pages/tabs/setting_page.dart';
import 'package:myflutterapp/res/widgets.dart';
/**
* 切換頁面
*/
class TabPage extends StatefulWidget {
const TabPage({Key? key}) : super(key: key);
@override
_TabPageState createState() => _TabPageState();
}
class _TabPageState extends State<TabPage> {
int countIndex = 0;
List list = [
HomePage(),
CategoryPage(),
SettingPage(),
MyPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Flutter Demo"),),
body: list[countIndex], //動態切換頁面
bottomNavigationBar: BottomNavigationBar(
//items中的View大於3就需要這個屬性fiexd,不然下面的圖標和文字會變白
type: BottomNavigationBarType.fixed,
//點擊事件
onTap:(int index){
setState(() {
this.countIndex = index;
print("$countIndex");
});
},
//當前選中的index 不設置這個就不會變化點擊item的顏色
currentIndex: countIndex,
fixedColor: Colors.pink,
//圖標的大小
iconSize: 24,
//選中文字的大小
selectedFontSize: 14,
//未選中文字的大小
unselectedFontSize: 12,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label:"首頁",
),
BottomNavigationBarItem(
icon: Icon(Icons.category),
label:"分類"
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label:"設置"
),
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
label:"我的"
),
],
),
);
}
}
上面的代碼可以看出body中沒做任何操作,就是單純的替換了頁面,這也是最直接的方式,這里用做的是tab頁面的切換,並不適合普通兩個頁面的跳轉。
三、舉例說明:基本路由+傳參數 官方文檔:https://docs.flutter.dev/cookbook/navigation/navigation-basics
核心api :
- Navigator.push() 或者Navigator.of(context).push() 跳轉和傳參數 MaterialPageRoute(builder: (context) => const SecondRoute(title:"你好flutter"))
- Navigator.pop() 返回 類似Android中的onBackPressed()方法
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class FirstRoute extends StatelessWidget {
const FirstRoute({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
// 若想傳參數 采用構造函數傳參數
// MaterialPageRoute(builder: (context) => const SecondRoute(title:"你好flutter")),
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
String title;
SecondRoute({this.title="",Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Route'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go back!'),
//使用接收的參數
// child: Text(this.title);
),
),
);
}
}
想要傳參數的話可以采用構造函數進行傳參數,如上面代碼中的注釋操作。
四、舉例說明:命名路由傳參+接收
1.普通命名路由
要求:
- 需要在MaterialApp下的routes進行注冊頁面
- Navigator.pushNamed("路由名稱") 進行跳轉
不傳遞參數:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
initialRoute: "/",
routes: {
"/":(context) => FirstScreen(),
"/second":(context) => SecondScreen(),
},
);
}
}
class FirstScreen extends StatelessWidget {
const FirstScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("FirstScreen"),),
body: Center(
child: ElevatedButton(
// Within the `FirstScreen` widget
onPressed: () {
// Navigate to the second screen using a named route.
Navigator.pushNamed(context, '/second');
},
child: const Text('Launch screen'),
),
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("SecondScreen"),),
body: Center(
child: ElevatedButton(
onPressed: (){
Navigator.pop(context);
},
child: Text("Go back!"),
),
),
);
}
}
傳遞參數:
要求:
- 需要在MaterialApp下的routes進行注冊頁面
- Navigator.pushNamed(context,"路由名稱","參數") 進行跳轉和傳參數
- 接收參數 ModalRoute.of(context)!.settings.arguments
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
initialRoute: "/",
routes: {
"/":(context) => FirstScreen(),
"/second":(context) => SecondScreen(),
},
);
}
}
class FirstScreen extends StatelessWidget {
const FirstScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("FirstScreen"),),
body: Center(
child: ElevatedButton(
// Within the `FirstScreen` widget
onPressed: () {
//定義傳遞的參數
Map<String,String> args = {
"title":"this is 傳遞的參數"
};
//arguments接收的Object參數可以是任意類型的,這里我們用Map類型
Navigator.pushNamed(context, '/second',arguments: args);
},
child: const Text('跳轉頁面並傳遞參數'),
),
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
//接收傳遞的參數
Map<String,String> args = ModalRoute.of(context)!.settings.arguments as Map<String,String>;
return Scaffold(
appBar: AppBar(title: Text("SecondScreen"),),
body: Center(
child: Container(
height: 200,
width: 200,
child: Column(
children: [
//接收的Map類型數據顯示到界面
Text("${args["title"]}"),
ElevatedButton(
onPressed: (){
Navigator.pop(context);
},
child: Text("Go back!"),
),
],
),
),
),
);
}
}
2.指定命名路由的跳轉 + 傳參(指定onGenerateRoute)官方案例:https://docs.flutter.dev/cookbook/navigation/navigate-with-arguments
特點:
- 不需要在routes中注冊路由
- 可以指定接收某個路由跳轉的參數做處理
- 雖然還是通過Navigator.pushNamed(context,"路由名稱","參數") 進行跳轉和傳參數但經過onGenerateRoute中處理
本質還是通過PassArgumentsScreen(title,message)構造函數在傳參數,只不過可以統一管理,頁面清晰
指定onGenerateRoute (單一,不推薦)
代碼:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: ,
//提供一個函數來處理命名路由。
//使用此功能識別正在推送的命名路由,並創建正確的屏幕。
onGenerateRoute: (settings) {
// 指定接收某個特定的 例如當前例子中的PassArguments 路由
if (settings.name == PassArgumentsScreen.routeName) {
// 強轉化參數
final args = settings.arguments as ScreenArguments;
// 然后,從參數中提取所需的數據並將數據傳遞到正確的屏幕。
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
// 該代碼目前僅支持 PassArgumentsScreen.routeName。如果我們添加它們,則需要實現其他值。這里的斷言將有助於提醒我們調用堆棧中更高的位置,因為否則該斷言會在框架中的某個地方觸發。
assert(false, '需要你實現路由頁面 ${settings.name}');
return null;
},
title: 'Navigation with Arguments',
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 導航到命名路由的按鈕。
// 對於此路由,提取 onGenerateRoute 函數中的參數並將它們傳遞到屏幕。
ElevatedButton(
onPressed: () {
//當用戶點擊按鈕時,導航到命名路由並提供參數作為可選參數。
Navigator.pushNamed(
context,
PassArgumentsScreen.routeName,
arguments: ScreenArguments(
'接受參數屏幕',
'此消息是在 onGenerateRoute函數中提取的。',
),
);
},
child: const Text('導航到接受參數的命名'),
),
],
),
),
);
}
}
// 通過構造函數接受必要參數的 Widget
// 這里通過構造函數接收的參數,
// 是由MyApp 中的MaterialApp的 onGenerateRoute返回的
//return MaterialPageRoute(
// builder: (context) {
// //通過構造函數進行傳參數的
// return PassArgumentsScreen(
// title: args.title,
// message: args.message,
// );
// },
// );
class PassArgumentsScreen extends StatelessWidget {
static const routeName = '/passArguments';
final String title;
final String message;
// 此 Widget 接受參數作為構造函數參數。
// 它不會從 ModalRoute 中提取參數。
// 參數由提供給 MaterialApp 小部件的 onGenerateRoute 函數提取。
const PassArgumentsScreen({
Key? key,
required this.title,
required this.message,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Text(message),
),
);
}
}
// 您可以將任何對象傳遞給 arguments 參數。
// 在此示例中,創建一個包含可自定義標題和消息的類。
// 這里是自定義需要的傳遞參數類型
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
使用通用的onGenerateRoute(通用,推薦)
代碼:
import 'package:flutter/material.dart';
void main() => runApp( MyApp());
class MyApp extends StatelessWidget {
//不依賴MaterialApp中的routes創建單獨的路由列表
//或者 final myRoutes 不加類型也是可以的
Map<String, WidgetBuilder>? myRoutes = {
"/":(context) => HomeScreen(),
"/second" : (context,{arguments}) => SecondScreen(arguments: arguments),
};
@override
Widget build(BuildContext context) {
return MaterialApp(
//提供一個函數來處理命名路由。
//使用此功能識別正在推送的命名路由,並創建正確的屏幕。
onGenerateRoute: (RouteSettings settings) {
//因為我們頁面跳轉和傳參數會有很多情況,
//於是做一個通用的路由接收是有必要的
//1.獲取當前調用的路由名稱
String? routeName = settings.name;
//2.找到我們上面定義的Map路由表中是否包含此路由,並取出方法
Function pageContentBuilder = this.myRoutes?[routeName] as Function;
//3.判斷不為null表示找到了某個路由定義的方法
if(pageContentBuilder != null) {
//4.判斷setting中的 arguments 不為null
if(settings.arguments != null){
//5.返回要跳轉的頁面和傳遞參數
final Route route = MaterialPageRoute(
//6.使用我們獲取到的Function 調用帶參數的去傳遞參數
builder:(context) => pageContentBuilder(context
,arguments:settings.arguments)
);
return route;
}else{
//7.否則使用我們獲取到的Function 調用不帶參數的
return MaterialPageRoute(
builder:(context) => pageContentBuilder(context)
);
}
}
},
title: 'Navigation with Arguments',
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 導航到命名路由的按鈕。
// 對於此路由,提取 onGenerateRoute 函數中的參數並將它們傳遞到屏幕。
ElevatedButton(
onPressed: () {
//當用戶點擊按鈕時,導航到命名路由並提供參數作為可選參數。
Navigator.pushNamed(
context,
'/second',
arguments: ScreenArguments(
'接收的參數標題',
'此消息是在 onGenerateRoute函數中提取的。',
),
);
},
child: const Text('導航到接受參數的命名'),
),
],
),
),
);
}
}
class SecondScreen extends StatelessWidget {
ScreenArguments? arguments;
SecondScreen({this.arguments,Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
//接收傳遞的參數
return Scaffold(
appBar: AppBar(title: Text("SecondScreen"),),
body: Center(
child: Container(
height: 200,
width: 400,
child: Column(
children: [
//使用接收到的數據顯示
Text("title: ${arguments?.title}"),
Text("message: ${arguments?.message}"),
ElevatedButton(
onPressed: (){
Navigator.pop(context);
},
child: Text("Go back!"),
),
],
),
),
),
);
}
}
// 您可以將任何對象傳遞給 arguments 參數。
// 在此示例中,創建一個包含可自定義標題和消息的類。
// 這里是自定義需要的傳遞參數類型
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
我們既然已經寫出來通用的路由onGenerateRoute的函數方法,那么我們可以兼顧通用性
單獨抽出這段代碼 (強烈推薦)
import 'package:flutter/material.dart';
import '../main.dart';
//定義路由列表
final Map<String,Function> routes={
"/":(context) => HomeScreen(),
"/second" : (context,{arguments}) => SecondScreen(arguments: arguments),
//未來只需要在這兒新增路由和方法就可以了
};
//定義通用的onGenerateRoute
var onGenerateRoute = (RouteSettings settings){
//settings 中兩個屬性 name 表示路由名稱 arguments 傳遞的參數若是無參則這個為null
//1.獲取當前調用的路由名稱
//2.找到我們上面定義的Map路由表中是否包含此路由,並取出方法
//3.取出對應的參數
//4.判斷不為null表示找到了某個路由定義的方法
//5.判斷setting中的 arguments 不為null
//6.返回要跳轉的頁面和傳遞參數
//7.使用我們獲取到的Function 調用帶參數的去傳遞參數
//8.否則使用我們獲取到的Function 調用不帶參數的
String? routeName = settings.name;
Function? pageContentBuilder = routes[routeName];
Object? args = settings.arguments;
if(pageContentBuilder != null){
if(args != null){
final Route route = MaterialPageRoute(builder: (context){
return pageContentBuilder(context,arguments:args);
});
return route;
}else{
return MaterialPageRoute(builder: (context) => pageContentBuilder(context));
}
}
};
在main.dart中使用
import 'package:flutter/material.dart';
import 'package:myflutterapp/route/routes.dart';
void main() => runApp( MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//使用通用的onGenerateRoute
onGenerateRoute: onGenerateRoute,
title: 'Navigation with Arguments',
home: const HomeScreen(),
);
}
}