目錄:
- 1、flutter路由和導航簡介
- 2、路由的使用
- 2.1、使用 Navigator
- 2.2、使用命名路由
- 2.3、使用路由器
- 3、應用中添加Tab導航
- 4、頁面跳轉一個新頁面和回退
- 5、傳遞數據到新頁面
- 6、使用 RouteSettings 傳遞參數
1、flutter路由和導航簡介
Flutter 提供了一個完整的系統,用于在屏幕之間導航和處理 深層鏈接。沒有復雜深度鏈接的小型應用程序可以使用 Navigator,而具有特定深度鏈接和導航的應用程序 要求還應該使用 Router 來正確處理 Android 和 iOS,并在應用程序運行時與地址欄保持同步 在 Web 上運行。
2、路由的使用
2.1、使用 Navigator
小組件使用正確的過渡將屏幕顯示為堆棧 動畫。要導航到新屏幕,請通過 route 訪問 并調用命令式方法。
child: const Text('Open second screen'),
onPressed: () {Navigator.of(context).push(MaterialPageRoute(builder: (context) => const SecondScreen()),);
},
因為會保留一堆對象(表示歷史記錄 stack),該方法還接受一個 object。
2.2、使用命名路由
child: const Text('Open second screen'),
onPressed: () {Navigator.pushNamed(context, '/second');
},
/second表示在列表中聲明的命名路由。有關完整示例,請按照 Flutter 說明書中的使用命名路由導航 recipe 進行作。MaterialApp.routes
局限性:
- 盡管命名路由可以處理深度鏈接,但行為始終相同,并且 無法自定義。當平臺收到新的深度鏈接時,Flutter 將新用戶推送到Navigator 上,而不管用戶當前位于何處。
- Flutter 也不支持使用 命名路由。由于這些原因,我們不建議在大多數 應用。
2.3、使用路由器
具有高級導航和路由要求(例如 使用指向每個屏幕的直接鏈接的 Web 應用程序,或具有多個小組件的應用程序)應使用路由包,例如 go_router 解析路由路徑并配置每當應用程序收到 new deep link 的 intent 值。
要使用 Router,請切換到 or 上的構造函數,并為其提供一個配置。路由包, 如 go_router,通常提供路由配置和路由 可以按如下方式使用:
child: const Text('Open second screen'),
onPressed: () => context.go('/second'),
因為像 go_router 這樣的包是聲明性的,所以它們將始終顯示 收到深度鏈接時的相同屏幕。
go_router的使用案例:
dependencies:flutter:sdk: fluttergo_router: ^x.y.z # 替換x.y.z為最新版本號
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';void main() {runApp(MyApp());
}class MyApp extends StatelessWidget { Widget build(BuildContext context) {final _router = GoRouter(initialLocation: '/',routes: [GoRoute(path: '/',builder: (context, state) => HomePage(),),GoRoute(path: '/details',builder: (context, state) => DetailsPage(),),],);return MaterialApp.router(routerConfig: _router,);}
}
- 創建頁面和導航
確保你已創建了相應的頁面(如HomePage和DetailsPage),這些將作為路由的目標。例如:
class HomePage extends StatelessWidget { Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Home')),body: Center(child: ElevatedButton(onPressed: () {context.go('/details'); // 使用context.go進行導航},child: Text('Go to Details'),),),);}
}
- 使用參數和嵌套路由(可選)
你可以在路由中使用參數,并設置嵌套路由:
GoRoute(path: '/user/:id', // 參數使用冒號標記builder: (context, state) {final userId = state.params['id']; // 獲取參數值return UserPage(userId: userId); // 將參數傳遞給頁面},routes: [ // 嵌套路由示例GoRoute(path: 'profile', builder: (context, state) => ProfilePage(), ),],
),
在頁面中使用參數:
class UserPage extends StatelessWidget {final String userId; // 頁面接收參數構造函數或初始化列表賦值方式之一// 構造函數接收參數userId 初始化列表賦值方式也行 如 final String userId; UserPage({Key? key, required this.userId}) : super(key: key); 然后在build中使用userId。 例如顯示在文本中:Text('User ID: $userId')。 嵌套路由的使用同理,你可以在ProfilePage中通過context訪問嵌套路由的參數。 例如:final profileId = state.params['profileId']; // 在ProfilePage的build方法中使用。 注意這里的state是從context獲取的,通常在build方法中通過ModalRoute.of(context).settings來獲取。 但由于我們使用了GoRouter,可以直接通過builder的state參數獲取。 例如在ProfilePage的build方法中可以直接使用String profileId = state.params['profileId']; 來獲取參數。 若要導航到嵌套路由,可以使用context.go('/user/${userId}/profile');。 若要在嵌套路由的頁面中使用非嵌套路由的參數,可以通過ModalRoute.of(context).settings.arguments來獲取。 但由于我們使用了GoRouter,可以直接通過state參數獲取。 若要在嵌套路由中導航,可以使用context.go('profile');(注意沒有前導斜杠)。 若要在非嵌套路由中使用嵌套路由的參數,可以在非嵌套路由的頁面中通過context.go('/user/${userId}'); 然后在這個路由的builder方法中通過state.subloc獲取嵌套路由的狀態,例如:StringUserPage({required this.userId});
3、應用中添加Tab導航
import 'package:flutter/material.dart';void main() {runApp(const TabBarDemo());
}class TabBarDemo extends StatelessWidget {const TabBarDemo({super.key}); Widget build(BuildContext context) {return MaterialApp(home: DefaultTabController(length: 3,child: Scaffold(appBar: AppBar(bottom: const TabBar(tabs: [Tab(icon: Icon(Icons.directions_car)),Tab(icon: Icon(Icons.directions_transit)),Tab(icon: Icon(Icons.directions_bike)),],),title: const Text('Tabs Demo'),),body: const TabBarView(children: [Icon(Icons.directions_car),Icon(Icons.directions_transit),Icon(Icons.directions_bike),],),),),);}
}
4、頁面跳轉一個新頁面和回退
- 用 Navigator.push() 跳轉到第二個路由
- 用 Navigator.pop() 回退到第一個路由
5、傳遞數據到新頁面
import 'package:flutter/material.dart';class Todo {final String title;final String description;const Todo(this.title, this.description);
}void main() {runApp(MaterialApp(title: 'Passing Data',home: TodosScreen(todos: List.generate(20,(i) => Todo('Todo $i','A description of what needs to be done for Todo $i',),),),),);
}class TodosScreen extends StatelessWidget {const TodosScreen({super.key, required this.todos});final List<Todo> todos; Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Todos')),body: ListView.builder(itemCount: todos.length,itemBuilder: (context, index) {return ListTile(title: Text(todos[index].title),// When a user taps the ListTile, navigate to the DetailScreen.// Notice that you're not only creating a DetailScreen, you're// also passing the current todo through to it.onTap: () {Navigator.push(context,MaterialPageRoute(builder: (context) => DetailScreen(todo: todos[index]),),);},);},),);}
}class DetailScreen extends StatelessWidget {// In the constructor, require a Todo.const DetailScreen({super.key, required this.todo});// Declare a field that holds the Todo.final Todo todo; Widget build(BuildContext context) {// Use the Todo to create the UI.return Scaffold(appBar: AppBar(title: Text(todo.title)),body: Padding(padding: const EdgeInsets.all(16),child: Text(todo.description),),);}
}
6、使用 RouteSettings 傳遞參數
import 'package:flutter/material.dart';class Todo {final String title;final String description;const Todo(this.title, this.description);
}void main() {runApp(MaterialApp(title: 'Passing Data',home: TodosScreen(todos: List.generate(20,(i) => Todo('Todo $i','A description of what needs to be done for Todo $i',),),),),);
}class TodosScreen extends StatelessWidget {const TodosScreen({super.key, required this.todos});final List<Todo> todos; Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Todos')),body: ListView.builder(itemCount: todos.length,itemBuilder: (context, index) {return ListTile(title: Text(todos[index].title),// When a user taps the ListTile, navigate to the DetailScreen.// Notice that you're not only creating a DetailScreen, you're// also passing the current todo through to it.onTap: () {Navigator.push(context,MaterialPageRoute(builder: (context) => const DetailScreen(),// Pass the arguments as part of the RouteSettings. The// DetailScreen reads the arguments from these settings.//通過此處來傳遞參數,其他地方同上settings: RouteSettings(arguments: todos[index]),),);},);},),);}
}class DetailScreen extends StatelessWidget {const DetailScreen({super.key}); Widget build(BuildContext context) {final todo = ModalRoute.of(context)!.settings.arguments as Todo;// Use the Todo to create the UI.return Scaffold(appBar: AppBar(title: Text(todo.title)),body: Padding(padding: const EdgeInsets.all(16),child: Text(todo.description),),);}
}