今回は、flutterを用いた画面遷移についてノート感覚でまとめていきたいと思います。
flutterでの画面遷移の詳細に入る前に、専門用語について説明します。
以上の専門用語をおさえたところで、画面遷移をどう実装していくのかを説明していきたいと思います。
画面遷移には、Navigator
というクラスを使用します。Navigator
はスタックの原理を用いて、ページウィジット(あるページを描画するウィジット)を管理するウィジットのことです。
それではどのようにして画面遷移をするのか説明していきたいと思います。
Navigator.push()
メソッドを使用します。push
されると元画面の上に新しい画面を積んでいくようなイメージです。
図で表すと以下のようになります。
元からあったfirst pageウィジットの上に新たな画面ウィジットsecond pageをプッシュすることによって表示する画面を切り替えているのです。
ある程度push
メソッドについてご理解いただけたと思うので、実際にpushメソッドの実装を見てみましょう。
static Future <T> push<T extends Object>(BuildContext context, Route<T> route)
push
メソッドはstatic
メソッドとなっているため、Navigator
クラスをインスタンス化しなくても使用可能なメソッドであることがわかります。
第一引数のcontext
には、MyApp
ウィジットのbuld
メソッドに渡しているcontext
が渡っているのではないかと考えています。build
メソッドの引数context
には、そのbuild
メソッドを実行することで返ってくるウィジット群の親ウィジット(Element)が入っています。context
についてはこちらの記事が参考になると思います。https://qiita.com/ko2ic/items/f7bf98b4a30049027470#build%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AEbuildcontext
第二引数に入っているRoute
が1画面を描画するためのウィジットです。大抵の場合、ここにはMaterialPageRoute
クラスを渡すこととなります。
逆に元の画面に戻りたい場合どうしたら良いでしょうか?
答えはNavigator.pop()
メソッドを使用します。このメソッドでは、上に積まれている画面ウィジットをNavigator
という箱から取り出していくイメージです。
図で表すと以下のようになります。
この図では、second pageを箱から取り出すことによってNavigator
という箱にはfirst pageのみが残っている状態となり、画面に描画されるのはfirst pageとなります。
画面遷移の実装を以下に示します。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext cotext) {
return MaterialApp(
title: "Flutter",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FirstPage(), //アプリを立ち上げた際に表示される初期画面
);
}
}
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("初期画面")),
body: Center(
child: RaisedButton(
//ボタンを押したら次のページに遷移するようにする
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return SecondPage();
}),
);
},
//ボタンの中に表示するテキスト
child: Text("次のページへ")),
),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("2番目の画面")),
body: Center(
child: RaisedButton(
//ボタンを押したら次のページに遷移するようにする
onPressed: () {
Navigator.pop(context);
},
//ボタンの中に表示するテキスト
child: Text("前のページへ")),
),
);
}
}
実装の結果を以下に示します。
「次のページへ」というボタンを押すと...
2番目の画面に遷移することができました。
「前の画面へ」というボタンを押すと...
初期画面に戻ることができました。
一応、画面遷移の実装ができました。
Fluttterには一つ一つの画面にURLのような名前をつけ、その名前を利用して画面遷移を行うことが可能です。
名前付きルートを使用することによって、先程の画面遷移の実装よりも再利用性の高いコーディングが可能になります。MaterialApp
内で以下のプロパティを設定します。この際、home
プロパティが使えなくなるのでご注意ください。initialRoute
プロパティには、アプリが起動した際に表示して欲しい初期画面を指定することが可能です。routes
プロパティには、それぞれのルートと対応するウィジットを指定することができます。
以上で定義したルートをpushNamed
メソッド内で使用することが可能です。
先程の「基本的な画面遷移」に比べてpush
メソッド内の記述が短くなる上に、ルートをグローバルな変数のように扱うことができるのでとても便利です。
以下に名前付きルートの実装を示します。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext cotext) {
return MaterialApp(
title: "Flutter",
theme: ThemeData(
primarySwatch: Colors.blue,
),
//名前付きルートの定義
initialRoute: "/",
routes: {
"/": (context) => FirstPage(),
"/second": (context) => SecondPage(),
},
);
}
}
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("初期画面")),
body: Center(
child: RaisedButton(
//名前付きルートを使用した画面遷移の部分(pushNamedを使用する)
onPressed: () {
Navigator.pushNamed(context, "/second");
},
//ボタンの中に表示するテキスト
child: Text("次のページへ")),
),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("2番目の画面")),
body: Center(
child: RaisedButton(
//ボタンを押したら次のページに遷移するようにする
onPressed: () {
Navigator.pop(context);
},
//ボタンの中に表示するテキスト
child: Text("前のページへ")),
),
);
}
}
上記の実装は「基本的な画面遷移」で実装した挙動と同じものです。MaterialApp
の中でhomeプロパティの設定ができなくなるかわりに、initialRoute
によって初期画面の指定がされているので初期画面の描画は問題なく行われます。
画面遷移をする際に、画面間でのデータのやり取りをしたい場面があります。
このセクションでは、「基本的な画面遷移」の実装をもとにデータの受け渡しについて説明していきたいと思います。
以前、onPressed
のMaterialPageRoute
の中のbuilder
プロパティで遷移先のページウィジットを指定すると説明しました。
この際、遷移先のページウィジットはコンストラクタによってクラスからインスタンスが生成されます。
このコンストラクタにデータを渡すことによって、遷移先にデータを送ることができます。
以下にコードを示します。
Navigator.push(
context,
MaterialPageRoute(
builder: (context){
//ここのコンストラクタの部分に引数としてデータを渡す
return SecondPage("渡したいデータ");
},
),
);
先程の実装ですと、SecondPageのコンストラクタは値を受け取るようにしていなかったので以下のように修正する必要があります。
class SecondPage extends StatelessWidget{
//メンバ変数の定義
final String data;
//コンストラクタの定義
SecondPage(this.data);
}
以上のように実装することでデータを受け渡すことが可能となります。
上記のコードのままですと、受け取れているのかわからないので以下のコードに変更しました。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext cotext) {
return MaterialApp(
title: "Flutter",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FirstPage(),
);
}
}
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("初期画面")),
body: Center(
child: RaisedButton(
//ボタンを押したら次のページに遷移するようにする
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
//ここのコンストラクタの部分に引数としてデータを渡す
return SecondPage("渡したいデータ");
},
),
);
},
//ボタンの中に表示するテキスト
child: Text("次のページへ")),
),
);
}
}
class SecondPage extends StatelessWidget {
//メンバ変数の定義
final String data;
//コンストラクタの定義
SecondPage(this.data);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("2番目の画面")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
//ボタンを押したら次のページに遷移するようにする
onPressed: () {
Navigator.pop(context);
},
//ボタンの中に表示するテキスト
child: Text("前のページへ")),
//ここでデータが渡ってきているのかどうかをみる
Text(this.data),
],
),
),
);
}
}
実行結果を以下に示します。
second pageにデータが渡ってきていることがわかりますね。
あるページに遷移するとき同様に、ページを戻る場合にもデータを受け渡すことが可能です。
ページを戻る時にデータを渡す場合、pop()メソッドの第2引数にデータを渡します。
以下にコードを示します。
Navigator.pop<String>(context, "戻る際に渡したいデータ");
そして、受け取ったデータはpush()メソッドの戻り値として返ってきます。
以下のように書くことでpush()メソッドの戻り値としてデータを受け取ることができます。
onPressed: () async {
const data = await Navigator.push(
context,
MaterialPageRoute(
builder: (context){
return SecondPage();
}
)
);
print(data);//受け取ったデータをコンソールに出力
}
実装した全体のコードを以下に示します。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext cotext) {
return MaterialApp(
title: "Flutter",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FirstPage(),
);
}
}
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("初期画面")),
body: Center(
child: RaisedButton(
//ボタンを押したら次のページに遷移するようにする
onPressed: () async {
var data = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return SecondPage();
},
),
);
print(data); //受け取ったデータをコンソールに出力
},
//ボタンの中に表示するテキスト
child: Text("次のページへ")),
),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("2番目の画面")),
body: Center(
child: RaisedButton(
//ボタンを押したら次のページに遷移するようにする
onPressed: () {
Navigator.pop<String>(context, "戻る際に渡したいデータ");
},
//ボタンの中に表示するテキスト
child: Text("前のページへ")),
),
);
}
}
実行結果を以下に示します。
初期画面です。
「次のページへ」をクリックしました。
「前のページへ」をクリックしました。
コンソール画面に「戻る際に渡したいデータ」と表示されていることから、うまくデータが渡っていることがわかります。
以上で画面遷移に関する説明を終わります。
お疲れ様でした。
・Flutter | Flutter documentation
https://flutter.dev/docs 参照日2021.8.29
・南里 勇気、太田 佳敬、矢田 祐基、片桐 寛貴『Flutter モバイルアプリ開発バイブル』(株式会社リクナビ出版、2019)
・石井幸次 『基礎から学ぶ Flutter』(株式会社シーアンドアール研究所)