大部分app包含多个屏幕来展示不同类型的信息. 如, 一个app可能会有一个屏幕用于展示产品. 用户可以点击产品图进入一个新的屏幕, 来获取详细信息.
术语: Flutter中screens 和 pages 称为routes(路由).
安卓中, route等同于Activity. iOS中, route等同于ViewController. Flutter中, route只是一个widget. 使用Navigator
导航到一个新的route.
通过下面步骤在两个路由的导航
-
- 创建两个路由 Create two routes
-
- 使用Navigator.push()导航到第二个路由 Navigate to the second route using Navigator.push()
-
- 使用Navigator.pop()返回第一个路由 Return to the first route using Navigator.pop()
创建两个路由, 每个路由有一个按钮. 点击第一个路由的按钮跳转到第二个路由. 点击第二个路由的按钮, 返回第一个路由.
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!'),
),
),
);
}
}
使用Navigator.push()方法, 来转到新的路由. push()方法将一个Route加入Navigator管理的stack中. 可以使用自己的Route, 也可以使用MaterialPageRoute. MaterialPageRoute很便利, 因为使用按照特定平台的动画.
// Within the `FirstRoute` widget
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
使用Navigator.pop()关闭第二个路由, 并返回第一个.pop()方法将当前路由从navigator管理的stack中移除.
// Within the SecondRoute widget
onPressed: () {
Navigator.pop(context);
}
通常, 我们会希望导航到新页面时, 能传递一些数据过去.
记住: Screen只是Widget. 该案例中, 会创建一个Todo列表. 当点击一个todo, 跳转到新的Screen(Widget), 把todo的信息显示出来.
-
- 定义Todo类
-
- 显示Todo列表
-
- 创建详情页, 来显示todo信息
-
- 导航并传递数据到详情页
class Todo {
final String title;
final String description;
Todo(this.title, this.description);
}
生成20个todo对象, 显示到ListView上
final todos = List<Todo>.generate(
20,
(i) => Todo(
'Todo $i',
'A description of what needs to be done for Todo $i',
),
);
ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
);
},
);
完整案例
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class Todo {
final String title;
final String description;
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 {
final List<Todo> todos;
TodosScreen({Key key, @required this.todos}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todos'),
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
// When a user taps on the ListTile, navigate to the DetailScreen.
// Notice that we're not only creating a DetailScreen, we're
// also passing the current todo through to it!
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(todo: todos[index]),
),
);
},
);
},
),
);
}
}
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 our UI
return Scaffold(
appBar: AppBar(
title: Text(todo.title),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(todo.description),
),
);
}
}
有些情况下, 需要将数据返回上一页. 例如, 跳转到的一个页面, 该页面中有两个选项. 当用户点击其中一个, 希望可以告知上一个页面用户的选择是哪个, 来做对应的操作.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Returning Data',
home: HomeScreen(),
));
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
body: Center(child: 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 will complete after we call
// Navigator.pop on the Selection Screen!
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")));
}
}
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: () {
// Close the screen and return "Yep!" as the result
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Nope!" as the result
Navigator.pop(context, 'Nope.');
},
child: Text('Nope.'),
),
)
],
),
),
);
}
}
给路由命名, 使用Navigator.pushNamed导航到指定的页面.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Named Routes Demo',
// Start the app with the "/" named route. In our case, the app will start
// on the FirstScreen Widget
initialRoute: '/',
routes: {
// When we navigate to the "/" route, build the FirstScreen Widget
'/': (context) => FirstScreen(),
// When we navigate to the "/second" route, build the SecondScreen Widget
'/second': (context) => SecondScreen(),
},
));
}
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Launch screen'),
onPressed: () {
// Navigate to the second screen using a named route
Navigator.pushNamed(context, '/second');
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to the first screen by popping the current route
// off the stack
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
在两个页面中展示同一张图. 当用户点击第一个页面的图时, 通过动画衔接跳转第二个页面.
为了使用动画衔接两个页面之间的跳转, 需要将两个页面的Image Widget包裹在一个Hero widget中. Hero需要两个参数:
- tag: Hero对象的标识. 两个页面必须保持一致.
- child: 希望在页面间做动画的Widget.
可以使用可重用的Hero对象, 以下案例为了演示方便重写了.
import 'package:flutter/material.dart';
void main() => runApp(HeroApp());
class HeroApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Transition Demo',
home: MainScreen(),
);
}
}
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
),
body: GestureDetector(
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://picsum.photos/250?image=9',
),
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailScreen();
}));
},
),
);
}
}
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Center(
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://picsum.photos/250?image=9',
),
),
),
onTap: () {
Navigator.pop(context);
},
),
);
}
}