在Flutter中,screen和pages都叫做路由(routes)。一个route就是一个widget,使用Navigator导航到新的一个route。
1.导航到新的页面并返回
两个route之间的导航由以下步骤:
- 创建两个route
- 使用Navigator.push()导航到第二个route
- 使用Navigator.pop()返回到第一个route
1.1 创建两个routes
class FirstRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
// Navigate to second route when tapped.
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to first route when tapped.
},
child: Text('Go back!'),
),
),
);
}
}
1.2 使用Navigator.push()导航到第二个route
// Within the `FirstRoute` widget
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
1.3 使用Navigator.pop()返回到第一个route
// Within the SecondRoute widget
onPressed: () {
Navigator.pop(context);
}
2. 使用命名routes进行导航
上面通过使用创建route并将其推送到导航器来导航到新的页面。
如果你的应用中需要由很多地方导航到同一个页面,如果在使用以上的方法那么代码就会重复。解决办法就是定义命名路由,通过命名路由进行导航。
使用命名路由步骤:
- 创建两个页面
- 定义routes
- 使用Navigator.pushNamed()导航到第二个页面
- 使用Navigator.pop()返回到第一个页面
2.1 创建两个页面
代码同上1.1
2.2 定义routes
通过MaterialApp构造函数提供的附加属性来定义routes:initialRoutes和routes它本身。
initialRoutes的属性定义应用程序从哪个route开始。
routes属性定义可用的命名路由以及导航到这些路由时要构建的widget。
MaterialApp(
// Start the app with the "/" named route. In this case, the app starts
// on the FirstScreen widget.
initialRoute: '/',
routes: {
// When navigating to the "/" route, build the FirstScreen widget.
'/': (context) => FirstScreen(),
// When navigating to the "/second" route, build the SecondScreen widget.
'/second': (context) => SecondScreen(),
},
);
2.3 使用Navigator.pushNamed()导航到第二个页面
// Within the `FirstScreen` widget
onPressed: () {
// Navigate to the second screen using a named route.
Navigator.pushNamed(context, '/second');
}
2.4 使用Navigator.pop()返回到第一个页面
代码同上1.3
3.将参数传递给命名路由
Navigator提供了使用公共标识符从应用程序的任何地方传递给命名路由。
使用Navigator.pushNamed()方法的arguments参数完成参数传递。
以下步骤将参数传递给命名路由并使用ModalRoute.of()和onGenerateRoute()读取参数:
- 定义需要传递的参数。
- 创建一个提取参数的widget。
- 在路由表中注册widget。
- 导航到widget。
3.1 定义需要传递的参数
// You can pass any object to the arguments parameter.
// In this example, create a class that contains a customizable
// title and message.
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
3.2 创建一个提取参数的widget
创建一个widget从ScreenArguments获取并显示title和message。要访问ScreenArguments,请使用ModalRoute.of()方法。此方法返回带有参数的当前路由。
// A widget that extracts the necessary arguments from the ModalRoute.
class ExtractArgumentsScreen extends StatelessWidget {
static const routeName = '/extractArguments';
@override
Widget build(BuildContext context) {
// Extract the arguments from the current ModalRoute settings and cast
// them as ScreenArguments.
final ScreenArguments args = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
title: Text(args.title),
),
body: Center(
child: Text(args.message),
),
);
}
}
3.3 在路由表中注册widget
MaterialApp(
routes: {
ExtractArgumentsScreen.routeName: (context) => ExtractArgumentsScreen(),
},
);
3.4 导航到widget
// A button that navigates to a named route. The named route
// extracts the arguments by itself.
RaisedButton(
child: Text("Navigate to screen that extracts arguments"),
onPressed: () {
// When the user taps the button, navigate to the specific route
// and provide the arguments as part of the RouteSettings.
Navigator.pushNamed(
context,
ExtractArgumentsScreen.routeName,
arguments: ScreenArguments(
'Extract Arguments Screen',
'This message is extracted in the build method.',
),
);
},
);
使用onGenerateRoute提取参数
也可以在onGenerateRoute()函数中提取参数并将它们传递给widget,而不是直接在窗口小部件中提取参数。
onGenerateRoute()函数根据给定的RouteSettings创建正确的路由。
MaterialApp(
// Provide a function to handle named routes. Use this function to
// identify the named route being pushed, and create the correct
// screen.
onGenerateRoute: (settings) {
// If you push the PassArguments route
if (settings.name == PassArgumentsScreen.routeName) {
// Cast the arguments to the correct type: ScreenArguments.
final ScreenArguments args = settings.arguments;
// Then, extract the required data from the arguments and
// pass the data to the correct screen.
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
},
);
4. 带数据从页面返回
有些时候我们需要从打开页面带数据返回到导航页面。
使用Navigator.pop()方法带回数据的步骤:
- 定义主屏幕
- 添加一个启动选择屏幕的按钮
- 使用两个按钮显示选择屏幕
- 点击按钮时,关闭选择屏幕
- 在主屏幕上显示选择的snackbar
4.1 定义主屏幕
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
// Create the SelectionButton widget in the next step.
body: Center(child: SelectionButton()),
);
}
}
4.2 添加一个启动选择屏幕的按钮
创建SelectionButton按钮,它将做以下两件事:
- 点击时打开选中页面
- 等待选中页面返回结果
class SelectionButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: Text('Pick an option, any option!'),
);
}
// A method that launches the SelectionScreen and awaits the
// result from Navigator.pop.
_navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that completes after calling
// Navigator.pop on the Selection Screen.
final result = await Navigator.push(
context,
// Create the SelectionScreen in the next step.
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
}
}
4.3 使用两个按钮显示选择屏幕
现在,构建一个包含两个按钮的选择屏幕。当用户点击按钮时,该应用关闭选择屏幕并让主屏幕知道点击了哪个按钮。
class SelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Pop here with "Yep"...
},
child: Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Pop here with "Nope"
},
child: Text('Nope.'),
),
)
],
),
),
);
}
}
4.4 点击按钮时,关闭选择屏幕
现在,更新两个按钮的onPressed()回调。要将数据返回到第一个屏幕,请使用Navigator.pop()方法,该方法接受名为result的可选第二个参数。任何结果都会在SelectionButton中返回到Future。
Yep button
RaisedButton(
onPressed: () {
// The Yep button returns "Yep!" as the result.
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
);
Nope button
RaisedButton(
onPressed: () {
// The Nope button returns "Nope!" as the result.
Navigator.pop(context, 'Nope!');
},
child: Text('Nope!'),
);
4.5 在主屏幕上显示选择的snackbar
现在您正在启动选择屏幕并等待结果,您将需要对返回的信息执行某些操作。
_navigateAndDisplaySelection(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
// After the Selection Screen returns a result, hide any previous snackbars
// and show the new result.
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$result")));
}
5. 将数据发送到新页面
通常,您不仅要导航到新屏幕,还要将数据传递到屏幕。例如,您可能希望传递有关已点击的项目的信息
请记住:屏幕只是小部件。在此示例中,创建待办事项列表。轻触待办事项时,导航到显示有关待办事项信息的新屏幕(widget)。此配方使用以下步骤:
- 定义待办事项类
- 显示待办事项列表
- 创建一个可以显示待办事项信息的详细信息屏幕
- 导航并将数据传递到详细信息屏幕
5.1 定义待办事项类
首先,您需要一种简单的方式来表示待办事项。对于此示例,请创建一个包含两个数据的类:标题和说明。
class Todo {
final String title;
final String description;
Todo(this.title, this.description);
}
5.2 显示待办事项列表
其次,显示待办事项列表。在此示例中,生成20个待办事项并使用ListView显示它们。
生成待办事项列表
final todos = List<Todo>.generate(
20,
(i) => Todo(
'Todo $i',
'A description of what needs to be done for Todo $i',
),
);
使用ListView显示待办事项列表
ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
);
},
);
5.3 创建一个可以显示待办事项信息的详细信息屏幕
现在,创建第二个屏幕。屏幕标题包含待办事项的标题,屏幕正文显示说明。
class DetailScreen extends StatelessWidget {
// Declare a field that holds the Todo.
final Todo todo;
// In the constructor, require a Todo.
DetailScreen({Key key, @required this.todo}) : super(key: key);
@override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(todo.title),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(todo.description),
),
);
}
}
5.4 导航并将数据传递到详细信息屏幕
有了DetailScreen,您就可以执行导航了。在此示例中,当用户点击列表中的待办事项时,导航到DetailScreen。将待办事项传递给DetailScreen。
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 to it.
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(todo: todos[index]),
),
);
},
);
},
);