大多數應用程序包含幾個用於顯示不一樣類型信息的屏幕 例如,咱們可能有一個顯示產品的屏幕。 而後,咱們的用戶能夠在新屏幕上點擊產品以獲取更多信息。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); }, ), ); } }