原文太长,拆成两篇,这一篇是说明,下一篇是练习。
本文介绍了 Flutter 的新 Navigator
和 Router
API 的工作原理。如果您遵循 Flutter 的开放设计文档,则可能已经看到了称为 Navigator 2.0 和 Router 的这些新功能。我们将探讨这些 API 如何对您的应用中的屏幕进行更精细的控制,以及如何使用它来解析路由。
这些新的 API 并没有改变,它们只是添加了一个新的声明性 API。在 Navigator 2.0 之前,很难推送或弹出多个页面,或删除当前页面下方的页面。但是,如果您对 Navigator
今天的工作方式感到满意,则可以继续以相同的方式(必需)使用它。
Router
提供了处理来自底层平台的路由并显示适当页面的能力。在本文中,将 Router
配置为解析浏览器 URL 以显示适当的页面。
本文可帮助您选择哪种 Navigator
模式最适合您的应用,并说明如何使用 Navigator 2.0 解析浏览器 URL 并完全控制活动页面的堆栈。本文中的练习显示了如何构建一个应用程序,该应用程序处理从平台传入的路由并管理应用程序的页面。以下 GIF 展示了实际的示例应用程序:
如果您使用的是 Flutter,则可能使用的是 Navigator
,并且熟悉以下概念:
Navigator
— 一个 widget,用于管理 Route 对象的堆栈。Route
— 由维护 screen 的 Navigator
管理的对象,通常由 MaterialPageRoute
之类的类实现。在 Navigator 2.0 之前,Routes
已通过命名路由或匿名路由 pushed and popped 到 Navigator
的堆栈中。下一节是对这两种方法的简要概述。
大多数移动应用程序将屏幕彼此叠放在一起,例如堆叠在一起。在 Flutter 中,使用 Navigator
很容易实现。
MaterialApp
和 CupertinoApp
在 hool 下使用 Navigator
。您可以使用 Navigator.of()
访问导航,或者使用 Navigator.push()
显示新屏,然后使用 Navigator.pop()
返回上一个屏:
import 'package:flutter/material.dart';
void main() {
runApp(Nav2App());
}
class Nav2App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
child: Text('View Details'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return DetailScreen();
}),
);
},
),
),
);
}
}
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
child: Text('Pop!'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
}
}
调用 push()
时,DetailScreen
小部件将放置在 HomeScreen
小部件的顶部,如下所示:
上一屏(HomeScreen
)仍是小部件树的一部分,因此与它关联的任何 State
对象都将停留在 DetailScreen
可见时。
Flutter 还支持命名路由,该路由在 MaterialApp
或 CupertinoApp
的 routes
参数中定义:
import 'package:flutter/material.dart';
void main() {
runApp(Nav2App());
}
class Nav2App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailScreen(),
},
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
child: Text('View Details'),
onPressed: () {
Navigator.pushNamed(
context,
'/details',
);
},
),
),
);
}
}
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
child: Text('Pop!'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
}
}
这些路由必须是预定义的。尽管您可以将参数传递给命名路由,但是您无法从路由本身解析参数。例如,如果该应用程序在网络上运行,则无法从 /details/:id
之类的路由中解析 ID。
处理命名路由的一种更灵活的方法是使用 onGenerateRoute
。该 API 使您能够处理所有路径:
onGenerateRoute: (settings) {
// Handle '/'
if (settings.name == '/') {
return MaterialPageRoute(builder: (context) => HomeScreen());
}
// Handle '/details/:id'
var uri = Uri.parse(settings.name);
if (uri.pathSegments.length == 2 &&
uri.pathSegments.first == 'details') {
var id = uri.pathSegments[1];
return MaterialPageRoute(builder: (context) => DetailScreen(id: id));
}
return MaterialPageRoute(builder: (context) => UnknownScreen());
},
这是完整的例子:
import 'package:flutter/material.dart';
void main() {
runApp(Nav2App());
}
class Nav2App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (settings) {
// Handle '/'
if (settings.name == '/') {
return MaterialPageRoute(builder: (context) => HomeScreen());
}
// Handle '/details/:id'
var uri = Uri.parse(settings.name);
if (uri.pathSegments.length == 2 &&
uri.pathSegments.first == 'details') {
var id = uri.pathSegments[1];
return MaterialPageRoute(builder: (context) => DetailScreen(id: id));
}
return MaterialPageRoute(builder: (context) => UnknownScreen());
},
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
child: Text('View Details'),
onPressed: () {
Navigator.pushNamed(
context,
'/details/1',
);
},
),
),
);
}
}
class DetailScreen extends StatelessWidget {
String id;
DetailScreen({
this.id,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Viewing details for item $id'),
FlatButton(
child: Text('Pop!'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
),
);
}
}
class UnknownScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text('404!'),
),
);
}
}
在这里,settings
是 RouteSettings
的一个实例。名称和参数字段是在调用 Navigator.pushNamed
或设置为 initialRoute
时提供的值。
Navigator 2.0 API 向框架中添加了新类,以使应用程序的屏幕具有应用程序状态的功能,并能够解析来自底层平台(如 Web URL)的路由。以下是新功能概述:
Page
— 一个不可变的(immutable)对象,用于设置 Navigator 的历史记录堆栈。Router
— 配置要由 Navigator 显示的页面列表。通常,此页面列表根据基础平台或应用程序更改状态而更改。RouteInformationParser
,它从 RouteInformationProvider
获取 RouteInformation
并将其解析为用户定义的数据类型。RouterDelegate
— 定义特定于应用程序的行为,例如路由器如何了解应用程序状态的更改以及如何响应更改。它的工作是监听 RouteInformationParser
和应用程序状态,并使用当前 Pages
列表构建 Navigator
。BackButtonDispatcher
— 向 Router
报告后退按钮按下情况。下图显示了 RouterDelegate
如何与 Router
,RouteInformationParser
和应用程序状态进行交互:
这是这些实例如何相互作用的一个例子:
RouteInformationParser
会将其转换为您在应用程序中定义的抽象数据类型 T
(例如,名为 BooksRoutePath
的类)。RouterDelegate
的 setNewRoutePath
方法,并且必须更新应用程序状态以反映更改(例如,通过设置 selectedBookId
)并调用 notifyListeners
。notifyListeners
时,它告诉路由器重建 RouterDelegate
(使用其 build()
方法)RouterDelegate.build()
返回一个新的 Navigator
,该 Navigator 的页面现在反映了对应用程序状态的更改(例如,selectedBookId
)。下一篇:Navigator 2.0 练习。