大多数应用程序包含几个用于显示不一样类型信息的屏幕 例如,咱们可能有一个显示产品的屏幕。 而后,咱们的用户能够在新屏幕上点击产品以获取更多信息。html
在Android条款中,咱们的屏幕将是新的活动。 在iOS中,新的ViewControllers。 在Flutter中,屏幕只是部件!java
那么咱们如何导航到新屏幕? 使用Navigator!git
路线github
首先,咱们须要两个屏幕来处理。 因为这是一个基本的例子,咱们将建立两个屏幕,每一个屏幕包含一个按钮。 点击第一个屏幕上的按钮将导航到第二个屏幕。 点击第二个屏幕上的按钮将使咱们的用户回到第一个!web
首先,咱们将设置视觉结构。app
class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('First Screen'), ), body: new Center( child: new RaisedButton( child: new Text('Launch new screen'), onPressed: () { // Navigate to second screen when tapped! }, ), ), ); } } class SecondScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Second Screen"), ), body: new Center( child: new RaisedButton( onPressed: () { // Navigate back to first screen when tapped! }, child: new Text('Go back!'), ), ), ); } }
为了导航到新的屏幕,咱们须要使用Navigator.push方法。 push方法会将Route添加到由导航器管理的路由堆栈中!less
push方法须要Route,但Route从哪里来? 咱们能够建立本身的,或者使用MaterialPageRoute开箱即用。 MaterialPageRoute很方便,由于它使用平台特定的动画转换到新屏幕。async
在咱们的FirstScreen部件的build方法中,咱们将更新onPressed回调:ide
// Within the `FirstScreen` Widget onPressed: () { Navigator.push( context, new MaterialPageRoute(builder: (context) => new SecondScreen()), ); }
如今咱们在第二个屏幕上,咱们如何关闭它并返回到第一个屏幕? 使用Navigator.pop方法!pop方法将从由导航器管理的路线堆栈中移除当前Route。函数
对于这部分,咱们须要更新在SecondScreen部件中找到的onPressed回调
// Within the SecondScreen Widget onPressed: () { Navigator.pop(context); }
import 'package:flutter/material.dart'; void main() { runApp(new MaterialApp( title: 'Navigation Basics', home: new FirstScreen(), )); } class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('First Screen'), ), body: new Center( child: new RaisedButton( child: new Text('Launch new screen'), onPressed: () { Navigator.push( context, new MaterialPageRoute(builder: (context) => new SecondScreen()), ); }, ), ), ); } } class SecondScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("Second Screen"), ), body: new Center( child: new RaisedButton( onPressed: () { Navigator.pop(context); }, child: new Text('Go back!'), ), ), ); } }
一般,咱们不只要导航到新的屏幕,还要将一些数据传递到屏幕。 例如,咱们常常想传递关于咱们点击的项目的信息。
请记住:屏幕只是部件™。 在这个例子中,咱们将建立一个Todos列表。 当点击一个待办事项时,咱们将导航到一个显示关于待办事项信息的新屏幕(部件)。
路线
首先,咱们须要一种简单的方法来表示Todos。 在这个例子中,咱们将建立一个包含两部分数据的类:title和description。
class Todo { final String title; final String description; Todo(this.title, this.description); }
其次,咱们要显示一个Todos列表。 在这个例子中,咱们将生成20个待办事项并使用ListView显示它们。 有关使用列表的更多信息,请参阅基本列表配方。
生成Todos列表
final todos = new List<Todo>.generate( 20, (i) => new Todo( 'Todo $i', 'A description of what needs to be done for Todo $i', ), );
使用ListView显示Todos列表
new ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { return new ListTile( title: new Text(todos[index].title), ); }, );
到如今为止还挺好。 咱们将生成20个Todos并将它们显示在ListView中!
如今,咱们将建立咱们的第二个屏幕。 屏幕的标题将包含待办事项的title,屏幕正文将显示description。
因为这是一个普通的StatelessWidget,咱们只须要建立屏幕的用户传送Todo! 而后,咱们将使用给定的Todo来构建UI。
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 new Scaffold( appBar: new AppBar( title: new Text("${todo.title}"), ), body: new Padding( padding: new EdgeInsets.all(16.0), child: new Text('${todo.description}'), ), ); } }
经过咱们的DetailScreen,咱们准备好执行导航! 在咱们的例子中,当用户点击咱们列表中的Todo时,咱们须要导航到DetailScreen。 当咱们这样作时,咱们也想将Todo传递给DetailScreen。
为了达到这个目的,咱们将为咱们的ListTile部件编写一个onTap回调函数。 在咱们的onTap回调中,咱们将再次使用Navigator.push方法。
new ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { return new ListTile( title: new Text(todos[index].title), // When a user taps on the ListTile, navigate to the DetailScreen. // Notice that we're not only creating a new DetailScreen, we're // also passing the current todo to it! onTap: () { Navigator.push( context, new MaterialPageRoute( builder: (context) => new DetailScreen(todo: todos[index]), ), ); }, ); }, );
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(new MaterialApp( title: 'Passing Data', home: new TodosScreen( todos: new List.generate( 20, (i) => new 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 new Scaffold( appBar: new AppBar( title: new Text('Todos'), ), body: new ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { return new ListTile( title: new Text(todos[index].title), // When a user taps on the ListTile, navigate to the DetailScreen. // Notice that we're not only creating a new DetailScreen, we're // also passing the current todo through to it! onTap: () { Navigator.push( context, new MaterialPageRoute( builder: (context) => new 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 new Scaffold( appBar: new AppBar( title: new Text("${todo.title}"), ), body: new Padding( padding: new EdgeInsets.all(16.0), child: new Text('${todo.description}'), ), ); } }
在某些状况下,咱们可能想要重新屏幕返回数据。 例如,假设咱们推出一个新的屏幕,向用户呈现两个选项。 当用户点击某个选项时,咱们须要通知第一个屏幕用户的选择,以便它可以处理这些信息!
咱们怎样才能作到这一点? 使用Navigator.pop!
路线
主屏幕将显示一个按钮。 点击后,它将启动选择屏幕!
class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Returning Data Demo'), ), // We'll create the SelectionButton Widget in the next step body: new Center(child: new SelectionButton()), ); } }
如今,咱们将建立咱们的SelectionButton。 咱们的选择按钮将会:
class SelectionButton extends StatelessWidget { @override Widget build(BuildContext context) { return new RaisedButton( onPressed: () { _navigateAndDisplaySelection(context); }, child: new 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, // We'll create the SelectionScreen in the next step! new MaterialPageRoute(builder: (context) => new SelectionScreen()), ); } }
如今,咱们须要构建一个选择屏幕! 它将包含两个按钮。 当用户点击按钮时,应该关闭选择屏幕并让主屏幕知道哪一个按钮被点击!
如今,咱们将定义UI,并肯定如何在下一步中返回数据。
class SelectionScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Pick an option'), ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Padding( padding: const EdgeInsets.all(8.0), child: new RaisedButton( onPressed: () { // Pop here with "Yep"... }, child: new Text('Yep!'), ), ), new Padding( padding: const EdgeInsets.all(8.0), child: new RaisedButton( onPressed: () { // Pop here with "Nope" }, child: new Text('Nope.'), ), ) ], ), ), ); } }
如今,咱们要更新两个按钮的onPressed回调! 为了将数据返回到第一个屏幕,咱们须要使用Navitator.pop方法。
Navigator.pop接受一个可选的第二个参数result。 若是咱们提供了result,它将在咱们的SelectionButton中返回到Future!
Yep 按钮
new RaisedButton( onPressed: () { // Our Yep button will return "Yep!" as the result Navigator.pop(context, 'Yep!'); }, child: new Text('Yep!'), );
Nope 按钮
new RaisedButton( onPressed: () { // Our Nope button will return "Nope!" as the result Navigator.pop(context, 'Nope!'); }, child: new Text('Nope!'), );
既然咱们正在启动一个选择屏幕并等待结果,那么咱们会想要对返回的信息进行一些操做!
在这种状况下,咱们将显示一个显示结果的Snackbar。 为此,咱们将更新SelectionButton中的_navigateAndDisplaySelection方法。
_navigateAndDisplaySelection(BuildContext context) async { final result = await Navigator.push( context, new MaterialPageRoute(builder: (context) => new SelectionScreen()), ); // After the Selection Screen returns a result, show it in a Snackbar! Scaffold .of(context) .showSnackBar(new SnackBar(content: new Text("$result"))); }
import 'package:flutter/material.dart'; void main() { runApp(new MaterialApp( title: 'Returning Data', home: new HomeScreen(), )); } class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Returning Data Demo'), ), body: new Center(child: new SelectionButton()), ); } } class SelectionButton extends StatelessWidget { @override Widget build(BuildContext context) { return new RaisedButton( onPressed: () { _navigateAndDisplaySelection(context); }, child: new 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, new MaterialPageRoute(builder: (context) => new SelectionScreen()), ); // After the Selection Screen returns a result, show it in a Snackbar! Scaffold .of(context) .showSnackBar(new SnackBar(content: new Text("$result"))); } } class SelectionScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Pick an option'), ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Padding( padding: const EdgeInsets.all(8.0), child: new RaisedButton( onPressed: () { // Close the screen and return "Yep!" as the result Navigator.pop(context, 'Yep!'); }, child: new Text('Yep!'), ), ), new Padding( padding: const EdgeInsets.all(8.0), child: new RaisedButton( onPressed: () { // Close the screen and return "Nope!" as the result Navigator.pop(context, 'Nope.'); }, child: new Text('Nope.'), ), ) ], ), ), ); } }
在屏幕之间导航时,指导用户浏览咱们的应用一般颇有帮助。 经过应用引导用户的经常使用技术是将部件从一个屏幕动画到下一个屏幕。 这会建立一个链接两个屏幕的视觉锚点。
咱们如何使用Flutter将部件从一个屏幕动画到下一个屏幕? 使用Hero部件!
路线
在这个例子中,咱们将在两个屏幕上显示相同的图像。 当用户点击图像时,咱们但愿将图像从第一个屏幕动画到第二个屏幕。 如今,咱们将建立视觉结构,并在接下来的步骤中处理动画!
class MainScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Main Screen'), ), body: new GestureDetector( onTap: () { Navigator.push(context, new MaterialPageRoute(builder: (_) { return new DetailScreen(); })); }, child: new Image.network( 'https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes/images/lake.jpg', ), ), ); } } class DetailScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( body: new GestureDetector( onTap: () { Navigator.pop(context); }, child: new Center( child: new Image.network( 'https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes/images/lake.jpg', ), ), ), ); } }
为了用动画将两个屏幕链接起来,咱们须要在两个屏幕上的Hero部件中包装Image部件。 Hero部件须要两个参数:
new Hero( tag: 'imageHero', child: new Image.network( 'https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes/images/lake.jpg', ), );
要完成与第一个屏幕的链接,咱们须要使用Hero部件将Image封装在第二个屏幕上! 它必须使用与第一个屏幕相同的tag。
将Hero部件应用到第二个屏幕后,屏幕之间的动画将起做用!
new Hero( tag: 'imageHero', child: new Image.network( 'https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes/images/lake.jpg', ), );
注意:此代码与咱们在第一个屏幕上的代码相同! 一般,您能够建立可重用的部件,而不是重复代码,但对于此示例,咱们将复制代码以进行演示。
import 'package:flutter/material.dart'; void main() => runApp(new HeroApp()); class HeroApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Transition Demo', home: new MainScreen(), ); } } class MainScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Main Screen'), ), body: new GestureDetector( child: new Hero( tag: 'imageHero', child: new Image.network( 'https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes/images/lake.jpg', ), ), onTap: () { Navigator.push(context, new MaterialPageRoute(builder: (_) { return new DetailScreen(); })); }, ), ); } } class DetailScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( body: new GestureDetector( child: new Center( child: new Hero( tag: 'imageHero', child: new Image.network( 'https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes/images/lake.jpg', ), ), ), onTap: () { Navigator.pop(context); }, ), ); } }