Flutter の画面遷移では Navigator を使用します。
Navigator は、ウィジェットをスタックで管理します。
Navigator を使用して以下のような画面遷移を実現してみたいと思います。
※ボックスが画面、矢印が画面遷移、(赤文字)が操作イベント、[青文字]がNavigatorのメソッドです。
-
スプラッシュやログイン画面の様な一方通行の画面遷移の場合は、pushReplacement を使用します。
この場合、ログイン後はホーム画面がスタックのルートの画面になります。Navigator.of(context).pushReplacementNamed("/home");
-
ホーム画面からその他画面、またその他画面への遷移し戻ることが出来るようにする場合は、次の画面への遷移に push、画面を戻るときは pop を使用します。ダイアログもウィジェットであるため同じです。
push された画面では pop することが可能であれば、バックオペレーションが可能になります。※アクションバーの戻るボタンNavigator.of(context).pushNamed("/next");
-
スタックされたその他画面から、すべての画面をクリアしてホーム画面へ戻りたい場合は、popUntil を使用します。 popUntil は条件に一致するまでスタックから画面をpopして行きます。第二引数の predicate にPopを止めたい条件を指定します。
Navigator.popUntil(context, ModalRoute.withName("/home"));
-
ダイアログを表示する場合は、showDialogを使用します。showDialog の内部コードでは、push が呼ばれています。ダイアログを閉じる場合は、pop を使用します。
showDialog<bool>( context: context, builder: (BuildContext context) { return new AlertDialog( content: const Text('Do you want logout?'), actions: <Widget>[ new FlatButton( child: const Text('No'), onPressed: () { Navigator.of(context).pop(false); }, ), new FlatButton( child: const Text('Yes'), onPressed: () { Navigator.of(context).pop(true); }, ), ], ); }, );
-
ログアウトで、全ての画面を破棄してスプラッシュを表示する場合は、
pushAndRemoveUntil を使用します。pushAndRemoveUntil は条件に一致するまでスタックから画面を除いて行きます。その後画面を pushします。第二引数の predicate を false にすることで全て消されることになります。Navigator.pushAndRemoveUntil( context, new MaterialPageRoute( builder: (context) => new Splash()), (_) => false);
以下は全体のコードになりますので、動かしてみて下さい。
全体コード
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
title: 'Navigation with Routes',
routes: <String, WidgetBuilder>{
'/': (_) => new Splash(),
'/login': (_) => new Login(),
'/home': (_) => new Home(),
'/next': (_) => new Next(),
},
));
}
// ---------
// スプラッシュ
// ---------
class Splash extends StatefulWidget {
@override
_SplashState createState() => new _SplashState();
}
class _SplashState extends State<Splash> {
@override
void initState() {
super.initState();
new Future.delayed(const Duration(seconds: 3))
.then((value) => handleTimeout());
}
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
// TODO: スプラッシュアニメーション
child: const CircularProgressIndicator(),
),
);
}
void handleTimeout() {
// ログイン画面へ
Navigator.of(context).pushReplacementNamed("/login");
}
}
// ---------
// ログイン画面
// ---------
class Login extends StatefulWidget {
@override
_LoginState createState() => new _LoginState();
}
class _LoginState extends State<Login> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text("Login"),
),
body: new Center(
child: new Form(
child: new SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const SizedBox(height: 24.0),
new TextFormField(
decoration: const InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'User Id',
),
),
const SizedBox(height: 24.0),
new TextFormField(
maxLength: 8,
decoration: new InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'Password',
),
),
const SizedBox(height: 24.0),
new Center(
child: new RaisedButton(
child: const Text('Login'),
onPressed: () {
// TODO: ログイン処理
// ホーム画面へ
Navigator.of(context).pushReplacementNamed("/home");
},
),
),
],
),
),
),
),
);
}
}
// ---------
// ホーム画面
// ---------
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text("Home"),
),
body: new Center(
child: new RaisedButton(
child: const Text("Launch Next Screen"),
onPressed: () {
// その他の画面へ
Navigator.of(context).pushNamed("/next");
},
),
),
);
}
}
// ---------
// その他画面
// ---------
class Next extends StatefulWidget {
@override
_NextState createState() => new _NextState();
}
class _NextState extends State<Next> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text("Next"),
),
body: new Center(
child: new Column(
children: <Widget>[
const SizedBox(height: 24.0),
new RaisedButton(
child: const Text("Launch Next Screen"),
onPressed: () {
// その他の画面へ
Navigator.of(context).pushNamed("/next");
},
),
const SizedBox(height: 24.0),
new RaisedButton(
child: const Text("Home"),
onPressed: () {
// ホーム画面へ戻る
Navigator.popUntil(context, ModalRoute.withName("/home"));
},
),
const SizedBox(height: 24.0),
new RaisedButton(
child: const Text("Logout"),
onPressed: () {
// 確認ダイアログ表示
showDialog<bool>(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
content: const Text('Do you want logout?'),
actions: <Widget>[
new FlatButton(
child: const Text('No'),
onPressed: () {
// 引数をfalseでダイアログ閉じる
Navigator.of(context).pop(false);
},
),
new FlatButton(
child: const Text('Yes'),
onPressed: () {
// 引数をtrueでダイアログ閉じる
Navigator.of(context).pop(true);
},
),
],
);
},
).then<void>((aBool) {
// ダイアログがYESで閉じられたら...
if (aBool) {
// 画面をすべて除いてスプラッシュを表示
Navigator.pushAndRemoveUntil(
context,
new MaterialPageRoute(
builder: (context) => new Splash()),
(_) => false);
}
});
},
),
],
),
),
);
}
}
参考
- Navigate to a new screen and back - Flutter https://flutter.io/cookbook/navigation/navigation-basics/
- Navigator class - widgets library - Dart API https://docs.flutter.io/flutter/widgets/Navigator-class.html
- Flutterの画面遷移 - Qiita
- FlutterでIntent.FLAG_ACTIVITY_CLEAR_TOPてきなことをやる - Qiita