Flutter 路由详解

目录


一、路由基础概念

1. 什么是路由?

路由(Route)是指应用中页面之间的导航机制,在Flutter中,路由是由Navigator组件管理的Route对象的堆栈。

2. 核心组件

  • Navigator:管理路由堆栈的核心类,负责页面的推入、弹出和替换
  • Route:表示一个页面的抽象类,常用实现有MaterialPageRouteCupertinoPageRoute
  • RouteSettings:包含路由的配置信息,如路由名称和参数

3. 路由堆栈

  • 推入(push):将新页面添加到堆栈顶部
  • 弹出(pop):移除堆栈顶部的页面
  • 替换(replace):用新页面替换堆栈顶部的页面
  • 清空(popUntil):弹出页面直到指定条件

二、基本导航方式

1. 基本导航

推入新页面

1
2
3
4
5
6
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(),
),
);

返回上一页

1
Navigator.pop(context);

带返回值的导航

1
2
3
4
5
6
7
8
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SelectionScreen(),
),
);
// 处理返回结果
print('Result: $result');

2. 替换路由

1
2
3
4
5
6
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => NewScreen(),
),
);

3. 移除指定路由

1
2
3
4
Navigator.popUntil(
context,
ModalRoute.withName('/home'),
);

4. 清空堆栈并导航

1
2
3
4
5
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => LoginScreen()),
(Route<dynamic> route) => false, // 移除所有路由
);

三、路由传参详解

1. 构造函数传参

传递参数

1
2
3
4
5
6
7
8
9
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(
id: 1,
title: 'Flutter Demo',
),
),
);

接收参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class DetailScreen extends StatelessWidget {
final int id;
final String title;

const DetailScreen({Key? key, required this.id, required this.title})
: super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(child: Text('ID: $id')),
);
}
}

2. RouteSettings传参

传递参数

1
2
3
4
5
6
7
8
9
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(
arguments: {'id': 1, 'title': 'Flutter Demo'},
),
builder: (context) => DetailScreen(),
),
);

接收参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DetailScreen extends StatefulWidget {
@override
_DetailScreenState createState() => _DetailScreenState();
}

class _DetailScreenState extends State<DetailScreen> {
late Map<String, dynamic> _args;

@override
void didChangeDependencies() {
super.didChangeDependencies();
_args = ModalRoute.of(context)?.settings.arguments as Map;
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(_args['title'])),
body: Center(child: Text('ID: ${_args['id']}')),
);
}
}

3. 类型安全的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ScreenArguments {
final int id;
final String title;

ScreenArguments(this.id, this.title);
}

// 传递
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(
arguments: ScreenArguments(1, 'Flutter Demo'),
),
builder: (context) => DetailScreen(),
),
);

// 接收
final args = ModalRoute.of(context)?.settings.arguments as ScreenArguments;

四、命名路由管理

1. 基本配置

1
2
3
4
5
6
7
8
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/second': (context) => SecondScreen(),
'/detail': (context) => DetailScreen(),
},
);

2. 导航到命名路由

1
Navigator.pushNamed(context, '/second');

3. 带参数的命名路由

1
2
3
4
5
Navigator.pushNamed(
context,
'/detail',
arguments: ScreenArguments(1, 'Flutter Demo'),
);

4. onGenerateRoute

用于处理未在routes中注册的路由,或需要动态生成的路由:

1
2
3
4
5
6
7
8
9
10
11
12
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == '/detail') {
final args = settings.arguments as ScreenArguments;
return MaterialPageRoute(
builder: (context) => DetailScreen(args: args),
);
}
// 处理其他路由...
return MaterialPageRoute(builder: (context) => NotFoundScreen());
},
);

5. onUnknownRoute

onGenerateRoute无法处理路由时调用:

1
2
3
4
5
MaterialApp(
onUnknownRoute: (settings) {
return MaterialPageRoute(builder: (context) => NotFoundScreen());
},
);

五、路由守卫与拦截

1. 自定义路由拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class AuthGuard extends StatelessWidget {
final Widget child;

const AuthGuard({Key? key, required this.child}) : super(key: key);

@override
Widget build(BuildContext context) {
final isLoggedIn = AuthService.isLoggedIn();

if (!isLoggedIn) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => LoginScreen()),
);
});
}

return child;
}
}

2. 路由监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class RouteObserverExample extends StatefulWidget {
@override
_RouteObserverExampleState createState() => _RouteObserverExampleState();
}

class _RouteObserverExampleState extends State<RouteObserverExample> with RouteAware {
static final routeObserver = RouteObserver<PageRoute>();

@override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context)! as PageRoute);
}

@override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}

@override
void didPush() {
// 路由被推入时调用
print('Route pushed');
}

@override
void didPop() {
// 路由被弹出时调用
print('Route popped');
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Route Observer')),
body: Center(child: Text('Route Observer Example')),
);
}
}

// 在MaterialApp中注册
MaterialApp(
navigatorObservers: [RouteObserverExample.routeObserver],
);

六、深度链接与通用链接

1. 配置深度链接

Android配置 (AndroidManifest.xml):

1
2
3
4
5
6
7
8
9
10
11
12
13
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="example.com"
android:pathPrefix="/detail" />
</intent-filter>
</activity>

iOS配置 (Info.plist):

1
2
3
4
5
6
7
8
9
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>

2. 处理深度链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MaterialApp(
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name?.startsWith('/detail/') ?? false) {
// 解析路径参数
final pathSegments = Uri.parse(settings.name!).pathSegments;
final id = pathSegments[1];
return MaterialPageRoute(
builder: (context) => DetailScreen(id: id),
);
}
// 其他路由处理...
},
);

3. 使用uni_links库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import 'package:uni_links/uni_links.dart';

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
StreamSubscription? _linkSubscription;

@override
void initState() {
super.initState();
_initDeepLinks();
}

void _initDeepLinks() async {
// 处理初始链接
final initialLink = await getInitialLink();
_handleDeepLink(initialLink);

// 监听后续链接
_linkSubscription = linkStream.listen((String? link) {
_handleDeepLink(link);
});
}

void _handleDeepLink(String? link) {
if (link != null) {
final uri = Uri.parse(link);
if (uri.path.startsWith('/detail')) {
final id = uri.queryParameters['id'];
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailScreen(id: id)),
);
}
}
}

@override
void dispose() {
_linkSubscription?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return MaterialApp(
// ...
);
}
}

七、路由动画与过渡效果

1. 自定义页面过渡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.ease;

var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));

return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
),
);

2. 预定义过渡效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 淡入淡出
Navigator.push(
context,
PageRouteBuilder(
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
),
);

// 缩放
Navigator.push(
context,
PageRouteBuilder(
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return ScaleTransition(
scale: animation,
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
),
);

3. 平台特定过渡

1
2
3
4
5
6
7
8
9
10
11
// Android风格
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailScreen()),
);

// iOS风格
Navigator.push(
context,
CupertinoPageRoute(builder: (context) => DetailScreen()),
);

八、嵌套路由与Tab导航

1. 嵌套Navigator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class HomeScreen extends StatelessWidget {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Navigator(
key: _navigatorKey,
initialRoute: '/home',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/home':
return MaterialPageRoute(builder: (context) => HomeContent());
case '/home/detail':
return MaterialPageRoute(builder: (context) => HomeDetail());
default:
return null;
}
},
),
);
}
}

2. Tab导航与路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
class TabNavigator extends StatelessWidget {
final String tabItem;
final GlobalKey<NavigatorState> navigatorKey;

const TabNavigator({
Key? key,
required this.tabItem,
required this.navigatorKey,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (settings) {
Widget child;
switch (settings.name) {
case '/':
child = getTabScreen(tabItem);
break;
case '/detail':
child = DetailScreen();
break;
default:
child = getTabScreen(tabItem);
}
return MaterialPageRoute(builder: (context) => child);
},
);
}

Widget getTabScreen(String tabItem) {
switch (tabItem) {
case 'home':
return HomeScreen();
case 'profile':
return ProfileScreen();
default:
return HomeScreen();
}
}
}

class MainScreen extends StatefulWidget {
@override
_MainScreenState createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
String _currentTab = 'home';
final Map<String, GlobalKey<NavigatorState>> _navigatorKeys = {
'home': GlobalKey<NavigatorState>(),
'profile': GlobalKey<NavigatorState>(),
};

void _selectTab(String tabItem) {
if (tabItem == _currentTab) {
_navigatorKeys[tabItem]?.currentState?.popUntil((route) => route.isFirst);
} else {
setState(() => _currentTab = tabItem);
}
}

@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab = !await _navigatorKeys[_currentTab]!.currentState!.maybePop();
if (isFirstRouteInCurrentTab) {
if (_currentTab != 'home') {
_selectTab('home');
return false;
}
}
return isFirstRouteInCurrentTab;
},
child: Scaffold(
body: Stack(children: [
_buildOffstageNavigator('home'),
_buildOffstageNavigator('profile'),
]),
bottomNavigationBar: BottomNavigationBar(
currentIndex: ['home', 'profile'].indexOf(_currentTab),
onTap: (index) => _selectTab(['home', 'profile'][index]),
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
),
);
}

Widget _buildOffstageNavigator(String tabItem) {
return Offstage(
offstage: _currentTab != tabItem,
child: TabNavigator(
tabItem: tabItem,
navigatorKey: _navigatorKeys[tabItem]!,
),
);
}
}

九、路由性能优化

1. 延迟加载路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == '/heavy') {
return MaterialPageRoute(builder: (context) {
// 延迟加载重量级页面
return FutureBuilder(
future: Future.delayed(Duration(milliseconds: 100)),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return HeavyScreen();
}
return Center(child: CircularProgressIndicator());
},
);
});
}
// 其他路由...
},
);

2. 路由预加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class PreloadManager {
static final Map<String, Widget> _preloadedWidgets = {};

static Future<void> preload(String routeName) async {
switch (routeName) {
case '/detail':
_preloadedWidgets['/detail'] = DetailScreen();
break;
case '/settings':
_preloadedWidgets['/settings'] = SettingsScreen();
break;
}
}

static Widget? getPreloaded(String routeName) {
return _preloadedWidgets[routeName];
}
}

// 使用
await PreloadManager.preload('/detail');

// 导航时使用预加载的Widget
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PreloadManager.getPreloaded('/detail')!),
);

3. 路由缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class RouteCache {
static final Map<String, Widget> _cache = {};

static Widget getOrCreate(String routeName, Widget Function() creator) {
if (!_cache.containsKey(routeName)) {
_cache[routeName] = creator();
}
return _cache[routeName]!;
}

static void clear() {
_cache.clear();
}

static void remove(String routeName) {
_cache.remove(routeName);
}
}

// 使用
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RouteCache.getOrCreate(
'/detail',
() => DetailScreen(),
),
),
);

十、最佳实践

1. 路由管理架构

推荐的路由管理方式

  • 使用命名路由进行集中管理
  • 结合onGenerateRoute处理动态路由
  • 使用类型安全的参数传递

目录结构

1
2
3
4
5
6
7
8
9
10
lib/
├── routes/
│ ├── route_names.dart # 路由名称常量
│ ├── route_arguments.dart # 路由参数类型
│ └── route_generator.dart # 路由生成器
├── screens/
│ ├── home_screen.dart
│ ├── detail_screen.dart
│ └── ...
└── main.dart

2. 路由名称管理

1
2
3
4
5
6
7
// route_names.dart
class RouteNames {
static const String home = '/';
static const String detail = '/detail';
static const String settings = '/settings';
static const String profile = '/profile';
}

3. 路由参数类型

1
2
3
4
5
6
7
8
9
10
11
12
13
// route_arguments.dart
class DetailArguments {
final int id;
final String title;

DetailArguments(this.id, this.title);
}

class ProfileArguments {
final String userId;

ProfileArguments(this.userId);
}

4. 路由生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// route_generator.dart
import 'package:flutter/material.dart';
import 'package:your_app/screens/home_screen.dart';
import 'package:your_app/screens/detail_screen.dart';
import 'package:your_app/screens/settings_screen.dart';
import 'package:your_app/screens/profile_screen.dart';
import 'package:your_app/routes/route_names.dart';
import 'package:your_app/routes/route_arguments.dart';

class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case RouteNames.home:
return MaterialPageRoute(builder: (_) => HomeScreen());

case RouteNames.detail:
final args = settings.arguments as DetailArguments;
return MaterialPageRoute(
builder: (_) => DetailScreen(args: args),
);

case RouteNames.settings:
return MaterialPageRoute(builder: (_) => SettingsScreen());

case RouteNames.profile:
final args = settings.arguments as ProfileArguments;
return MaterialPageRoute(
builder: (_) => ProfileScreen(args: args),
);

default:
return _errorRoute();
}
}

static Route<dynamic> _errorRoute() {
return MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(title: Text('Error')),
body: Center(child: Text('Page not found')),
);
});
}
}

5. 应用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// main.dart
import 'package:flutter/material.dart';
import 'package:your_app/routes/route_generator.dart';
import 'package:your_app/routes/route_names.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Route Demo',
initialRoute: RouteNames.home,
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}

6. 导航工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class NavigationService {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

static Future<dynamic> navigateTo(String routeName, {dynamic arguments}) {
return navigatorKey.currentState!.pushNamed(routeName, arguments: arguments);
}

static void goBack() {
navigatorKey.currentState!.pop();
}

static Future<dynamic> replaceWith(String routeName, {dynamic arguments}) {
return navigatorKey.currentState!.pushReplacementNamed(routeName, arguments: arguments);
}

static void popUntil(String routeName) {
navigatorKey.currentState!.popUntil(ModalRoute.withName(routeName));
}

static Future<dynamic> pushAndRemoveUntil(String routeName, {dynamic arguments}) {
return navigatorKey.currentState!.pushNamedAndRemoveUntil(
routeName,
(Route<dynamic> route) => false,
arguments: arguments,
);
}
}

// 配置
MaterialApp(
navigatorKey: NavigationService.navigatorKey,
// ...
);

// 使用
NavigationService.navigateTo(RouteNames.detail, arguments: DetailArguments(1, 'Flutter'));

总结

Flutter的路由系统是一个强大而灵活的导航框架,通过掌握以下核心概念和技术,可以构建出流畅、高效的应用导航体验:

  1. 基础导航:掌握Navigator的基本操作
  2. 参数传递:选择合适的传参方式
  3. 命名路由:实现集中化的路由管理
  4. 路由守卫:处理权限和导航逻辑
  5. 深度链接:支持外部链接打开应用
  6. 路由动画:提升用户体验
  7. 嵌套路由:实现复杂的导航结构
  8. 性能优化:确保导航流畅性

通过合理的路由设计和管理,可以显著提升应用的用户体验和代码可维护性。希望这份指南能帮助你更好地理解和使用Flutter的路由系统。