今回読んだページ
個人的なまとめ
- 画面遷移は
Navigator.push()
を使う。 - 元の画面に戻るときは
Navigator.pop()
を使う。 - 名前付きRouteに遷移する際は、
Navigator.pushNamed()
を使う。 - 名前付きRouteに遷移するときに引数を渡す場合は、
Navigator.pushNamed()
にarguments
を指定する - 引数を読み出すときは、
ModalRoute.of(context).settings.arguments
で取り出す。 - 遷移したあとの画面から、元の画面へデータを渡したい時は、
Navigator.pop()
に第2引数を指定するとできる。
Animate a widget across screens
多くの場合、ユーザーが画面から画面へと移動するときに、アプリを通じてユーザーをガイドすることが役立ちます。
アプリを通じてユーザーをリードする一般的な技術は、ある画面から次の画面へとWidgetをアニメーションさせることです。
これにより、2つの画面を接続する視覚的なアンカーが作成されます。
Hero
widgetを使って、ある画面から次の画面へとアニメーションさせます。
このレシピには、以下のステップがあります:
- 同じ画像を表示する2つの画面を作る
- 1つ目の画面に
Hero
widgetを追加する - 2つ目の画面に
Hero
widgetを追加する
1. Create two screens showing the same image
この例では、両方の画面に同じ画像を表示させます。
ユーザーが画像をタップしたとき、1つ目の画面から2つ目の画面へと画像をアニメーションさせます。
ここでは、視覚的な構造を作成し、次のステップでアニメーションを取り扱います。
Note: この例は、Navigate to a new screen and backとHandle tapsに基づいて構築されます。
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
),
body: GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailScreen();
}));
},
child: Image.network(
'https://picsum.photos/250?image=9',
),
),
);
}
}
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Center(
child: Image.network(
'https://picsum.photos/250?image=9',
),
),
),
);
}
}
2. Add a Hero widget to the first screen
2つの画面をアニメーションでつなぐためには、両方のスクリーンのImage
widgetをHero
widgetでラップします。
Hero(
tag: 'imageHero',
child: Image.network(
'https://picsum.photos/250?image=9',
),
);
Hero
widgetは2つの引数を必要とします。
- tag:
Hero
を識別するオブジェクト。両方の画面で同じである必要があります。 - child: 画面を通してアニメーションするWidget。
3. Add a Hero widget to the second screen
1つ目の画面との接続を完了するには、1つ目の画面のHero
と同じtag
を持ったHero
widgetで、2つ目の画面のImage
widgetをラップします。
Hero(
tag: 'imageHero',
child: Image.network(
'https://picsum.photos/250?image=9',
),
);
Note: このコードは1つ目の画面のものと同じです。
ベストプラクティスとしては、コードを繰り返すのではなく、再利用可能なWidgetを作成することです。
この例では、簡単のため、両方のWidgetに同じコードを使用しています。
2つ目の画面にHero
widgetを適用すると、画面間のアニメーションが動きます。
Interactive Example
See the Pen flutter-navigate-animate-1 by popy1017 (@popy1017) on CodePen.
Navigate to a new screen and back
たいていのアプリには、異なるタイプの情報を表示するために、いくつかの画面があります。
例えば、あるアプリが製品を表示するための画面を持っています。
ユーザーが製品の画像をタップすると、製品に関する詳細が新しい画面に表示されます。
用語: Flutterでは、画面やページをRouteと呼んでいます。このレシピの残りの部分では、Routeと呼びます。
(この記事では、「ルート」と書くとRootと混同するのでRouteと表記します。)
Androidでは、routeはActivityと同等です。
iOSでは、routeはViewControllerと同等です。
Flutterでは、routeはwidgetです。
Navigator
を使い、新しいrouteに移動します。このレシピは以下のステップを使います。
次の数セクションで、以下の3ステップで2つのroute間を移動するやり方を示します:
- Create two routes.
- Navigate to the second route using Navigator.push()
- Return to the first route using Navigator.pop()
1. Create two routes.
まず、使用する2つのrouteを作成します。
これは基本的な例なので、各Routeは1つのボタンのみを含みます。
1つ目のRouteのボタンを押すと、2つ目のRouteに遷移します。
2つ目のRouteのボタンを押すと、1つ目のRouteに帰ります。
まずは、視覚的な構造をセットアップします:
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!'),
),
),
);
}
}
2. Navigate to the second route using Navigator.push()
新しいRouteに切り替えるためには、Navigator.push()
メソッドを使います。
push()
メソッドは、Navigator
によって管理されるRouteのスタックにRoute
を追加します。
Route
はどこからくるのでしょうか?
独自に作成するか、MaterialPageRoute
を使います。
プラットフォーム固有のアニメーションを使って新しいRouteに遷移するので、MaterialPageRoute
は便利です。
// Within the `FirstRoute` widget
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
3. Return to the first route using Navigator.pop()
どうやって2つ目のRouteを閉じて1つ目のRouteに戻るのでしょうか?
Navigator.pop()
メソッドを使います。
pop()
メソッドは、Navigator
によって管理されるRouteのスタックから現在のRoute
を削除します。
元のRouteへ戻るのを実装するには、SecondRoute
widgetのonPressed()
コールバックを更新します。
// Within the SecondRoute widget
onPressed: () {
Navigator.pop(context);
}
Interactive example
See the Pen flutter-navigate-animate-2 by popy1017 (@popy1017) on CodePen.
Navigate with named routes
「Navigation to a new screen and back」レシピでは、新しいRouteを作成し、Navigator
にプッシュすることで、新しい画面に遷移する方法を学びました。
しかし、アプリのたくさんの部分で同じ画面に遷移する必要がある場合、このアプローチはコードの重複を引き起こす可能性があります。
解決策は「名前付きRoute(named Route)」を定義することで、ナビゲーションにそれを使うことです。
名前付きRouteを使うためには、Navigator.pushNamed()
関数を使います。
この例では元のレシピから機能を複製し、次の手順で名前付きRouteを使用する方法を示します。
- Create two screens.
- Define the routes.
- Navigate to the second screen using
Navigator.pushNamed()
- Return to the first screen using
Navigator.pop()
1. Create two screens
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 when tapped.
},
),
),
);
}
}
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 first screen when tapped.
},
child: Text('Go back!'),
),
),
);
}
}
2. Define the routes
次に、MaterialApp
コンストラクタに追加のプロパティ(initialRoute
とroutes
)を設定して、Routeを定義します。
initialRoute
プロパティは、アプリがどのRouteで始まるかを定義します。
route
プロパティは、設定したRouteにナビゲートする際に利用可能な名前付きRouteとビルドするWidgetを定義します。
MaterialApp(
// "/"と名前が付けられたRouteからスタートする。
// この場合、アプリはFirstScreen widgetからスタートする
initialRoute: '/',
routes: {
// "/" routeにナビゲートするときは、FirstScreen widgetをビルドする
'/': (context) => FirstScreen(),
// "/second"routeにナビゲートするときは、SecondScrennをビルドする
'/second': (context) => SecondScreen(),
},
);
警告:
initialRoute
を使うときは、home
プロパティは定義しないでください。
3. Navigate to the second screen
WidgetとRouteを配置したら、Navigator.pushNamed()
メソッドを使ってナビゲーションをトリガーします。
routes
テーブルで定義されたWidgetを作成して画面を起動するようにFlutterに指示します。
FirstScreen
widgetのbuild()
メソッドのonPressed()
を更新します:
// Within the `FirstScreen` widget
onPressed: () {
// Navigate to the second screen using a named route.
Navigator.pushNamed(context, '/second');
}
4. Return to the first screen
1つ目の画面に戻るためには、Navigation.pop()
を使います。
// Within the SecondScreen widget
onPressed: () {
// Navigate back to the first screen by popping the current route
// off the stack.
Navigator.pop(context);
}
Pass arguments to a named route
Navigator
は共通の識別子を使ってアプリのどこからでも名前付きRouteにナビゲートする機能を提供します。
ある場合には、名前付きRouteに引数を渡す必要があるかもしれない。
例えば、/user
routeに遷移して、そのrouteにユーザーに関する情報を渡したいかもしれない。
Navigator.pushNamed()
メソッドのarguments
パラメータを使うことでこれを達成できる。
ModalRoute.of()
メソッドを使用するか、MaterialApp
やCupertinoApp
コンストラクタが提供するonGenerateRoute()
の中から引数を抽出する。
このレシピは、以下のステップにしたがって、名前付きRouteへの引数の渡し方とModalRoute.of()
とonGenerateRoute()
を使った引数の読み出し方を示す:
- Define the arguments you need to pass.
- Create a widget that extracts the arguments.
- Register the widget in the
routes
table. - Navigate to the widget.
1. Define the arguments you need to pass.
まず、新しいRouteに渡す必要がある引数を定義する。
この例では、2つのデータを渡す。: 画面のtitle
と、message
両方のデータを渡すために、この情報を保持するClassを作成する。
// arguments parameterにはどんなオブジェクトでも渡せる
// この例では、カスタマイズ可能なtitleとmessageを含んだClassを作る
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
2. Create a widget that extracts the arguments
次に、ScreenArguments
からtitle
とmessage
を抽出して表示するWidgetを作成します。
ScreenArguments
にアクセスするためには、ModalRoute.of()
メソッドを使います。
このメソッドは引数を持った現在のRouteを返します。
// ModalRouteから必要な引数を抽出するWidget
class ExtractArgumentsScreen extends StatelessWidget {
static const routeName = '/extractArguments';
@override
Widget build(BuildContext context) {
// 現在のModalRoute settingsから引数を抽出し、ScreenArgumentsにキャストする
final ScreenArguments args = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
title: Text(args.title),
),
body: Center(
child: Text(args.message),
),
);
}
}
3. Register the widget in the routes table
次に、MaterialApp
widgetに提供されたroutes
にエントリーを追加します。
routes
はRouteの名前に基づいて作成されるべきWidgetを定義します。
MaterialApp(
routes: {
ExtractArgumentsScreen.routeName: (context) => ExtractArgumentsScreen(),
},
);
4. Navigate to the widget
最後に、ユーザーがボタンをタップしたときに、Navigator.pushNamed()
を使って、ExtractArgumentsScreen
にナビゲートする。
arguments
プロパティを介してRouteに引数を渡します。
ExtractArgumentsScreen
はこれらの引数からtitle
とmessage
を抽出します。
// 名前付きRouteにナビゲートするボタン。
// 名前付きRouteは自身で引数を抽出する。
RaisedButton(
child: Text("Navigate to screen that extracts arguments"),
onPressed: () {
// ユーザーがボタンをタップしたとき、名前付きRouteにナビゲートして、
// 任意のパラメータとして引数を渡す
Navigator.pushNamed(
context,
ExtractArgumentsScreen.routeName,
arguments: ScreenArguments(
'Extract Arguments Screen',
'This message is extracted in the build method.',
),
);
},
),
Alternatively, extract the arguments using onGenerateRoute
Widget内で直接引数を抽出する代わりに、onGenerateRoute()
関数の中で引数を抽出し、Widgetに渡すこともできる。
onGenerateRoute()
関数は、与えられたRouteSettings
に基づいて正しいRouteを作成する。
MaterialApp(
// 名前付きRouteを扱う関数を提供する。この関数を使い、
// プッシュされた名前付きRouteを特定し、正しい画面を作成する
onGenerateRoute: (settings) {
// PassArguments routeをプッシュした場合
if (settings.name == PassArgumentsScreen.routeName) {
// 正しい型(ScreenArguments)に引数をキャストする
final ScreenArguments args = settings.arguments;
// そして、引数から必要なデータを抽出し、
// 正しい画面にデータを渡す
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
);
}
},
);
Interactive example
See the Pen flutter-navigate-animate-3 by popy1017 (@popy1017) on CodePen.
Return data from a screen
ある場合では、新しい画面からデータを返したいかもしれない。
例えば、ユーザーに対する2つのオプションがある新しい画面をプッシュするとします。
ユーザーがオプションをタップしたとき、ユーザーの選択を1つ目の画面に知らせて、その情報に基づいてアクションできるようにする必要があります。
以下の手順で、Navigator.pop()
メソッドを使うことでこれを行うことができます。
- Define the home screen
- Add a button that launches the selection screen
- Show the selection screen with two buttons
- When a button is tapped, close the selection screen
- Show a snackbar on the home screen with the selection
1. Define the home screen
ホーム画面はボタンを表示します。タップされると、選択画面を起動します。
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
// 次のステップでSelectionButton widgetを作成する
body: Center(child: SelectionButton()),
);
}
}
2. Add a button that launches the selection screen
次に、次のようなSelectionButtonを作成します。
- タップされたときにSelectionScreenが起動します。
- SelectionScreenが結果を返すのを待ちます。
class SelectionButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: Text('Pick an option, any option!'),
);
}
// SelectionScreenを起動し、Navigator.popからの結果を待つメソッド
_navigateAndDisplaySelection(BuildContext context) async {
// Navigator.pushは、SelectionScreenでNavigator.popが呼ばれたあとに
// 完了するFutureを返す。
final result = await Navigator.push(
context,
// Create the SelectionScreen in the next step.
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
}
}
3. Show the selection screen with two buttons
次に、2つのボタンを含む選択画面を作成する。
ユーザーがボタンをタップしたとき、アプリは選択画面を閉じ、ホーム画面にどのボタンがタップされたかを知らせる。
このステップはUIを定義する。次のステップで、データを返すコードを追加する。
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. When a button is tapped, close the selection screen
次に、両方のボタンのonPressed()
コールバックを更新する。
最初の画面にデータを返すためには、result
と呼ばれる2番目の任意の引数を受け付けるNavigator.pop()
メソッドを使います。
結果はSelectionButtonでFuture
に返されます。
RaisedButton(
onPressed: () {
// The Yep button returns "Yep!" as the result.
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
);
RaisedButton(
onPressed: () {
// The Nope button returns "Nope!" as the result.
Navigator.pop(context, 'Nope!');
},
child: Text('Nope!'),
);
5. Show a snackbar on the home screen with the selection
選択画面を起動し結果を待機しているので、返却された情報で何かを実行する必要があります。
この場合、SelectionButton
の中で_navigateAndDisplaySelection()
メソッドを使って、結果を表示するスナックバーを表示します。
_navigateAndDisplaySelection(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
// 選択画面が結果を返したあと、前のスナックバーを隠し、新しい結果を表示する
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$result")));
}
補足
上記のコードの最終行で登場する..
はCascade表記と呼ばれるDart固有の書き方?である。
Language tour | Dart
Cascadeを使うと、同じオブジェクトにたいして一連の操作を行うことができる。
関数呼び出しだけでなく、オブジェクトのフィールドにもアクセスすることができる。
以下のCascadeあり・なしのコードを見るとわかるが、一時的な変数を用意する必要がなくなるので、コードをきれいにすることができる。
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
querySelector('#confirm') // Get an object.
..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
Send data to a new screen
多くの場合、新しい画面に移動するだけでなく、画面いデータも渡したいでしょう。
例えば、タップされたアイテムの情報を渡したいかもしれません。
画面は単なるWidgetであることを覚えてください。
この例では。Todoリストを作ります。
Todoがタップされると、そのTodoに関する情報が表示される新しい画面(Widget)に遷移します。
このレシピは以下の手順で行われます:
- Define a todo class.
- Display a list of todos.
- Create a detail screen that can display information about a todo.
- Navigate and pass data to the detail screen.
1. Define a todo class
まず、Todoを表すシンプルな方法が必要です。
この例では、titleとdescriptionの2つのデータを含むクラスを作成します。
class Todo {
final String title;
final String description;
Todo(this.title, this.description);
}
2. Create a list of todos
次に、Todoのリストを表示します。
この例では、20個のTodoを作成し、ListViewを使って表示します。
リストの操作に関してはUse lists recipeを参照してください。
Generate the list of todos
final todos = List<Todo>.generate(
20,
(i) => Todo(
'Todo $i',
'A description of what needs to be done for Todo $i',
),
);
Display the list of todos using a ListView
ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
);
},
);
ここまでは順調です。これで、20個のTodoが作成され、ListViewに表示されます。
3. Create a detail screen to display information about a todo
次に、2つ目の画面を作成します。
画面のタイトルはTodoのタイトルを含み、画面のbodyには説明が表示されます。
詳細画面は通常のStatelessWidget
であるため、ユーザーにUIでTodoの入力を要求します。
そして、与えられたTodoを使ってUIを構築します。
class DetailScreen extends StatelessWidget {
// Todoを保持するフィールドを宣言する
final Todo todo;
// コンストラクタでは、Todoを必須とする。
// In the constructor, require a Todo.
DetailScreen({Key key, @required this.todo}) : super(key: key);
@override
Widget build(BuildContext context) {
// Todoを使ってUIを作成する
return Scaffold(
appBar: AppBar(
title: Text(todo.title),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(todo.description),
),
);
}
}
4. Navigate and pass data to the detail screen
DetailScreen
を配置したら、ナビゲーションを行う準備ができました。
この例では、ユーザーがリストのTodoをタップするとDetailScreen
に遷移します。
DetailScreen
にはTodoを渡します。
ユーザーのタップをキャプチャするには、ListTile
widgetでonTap()
コールバックを書きます。
onTap()
コールバックの中で、Navigator.push()
関数を使います。
ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
// ユーザーがタップしたら、DetailScreenに遷移する
// DetailScreenを作成しているだけでなく、
// 現在のTodoを渡していることに注意してください
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(todo: todos[index]),
),
);
},
);
},
);
Interactive example
See the Pen flutter-navigate-animate-4 by popy1017 (@popy1017) on CodePen.