はじめに
先日Flutter製チャットアプリを支える技術でチャットアプリを作ったときの内容を書いて今年は終わりかなと思っていたら、こちらの枠が空いていたので前回触れなかった画面遷移について書きたいと思います。
Flutterの画面遷移についてはNavigation & routingを見ると基本的には良いと思います。
ですが、実際アプリを作った際にこれだけでは足りないと思いますので、画面遷移をiOS・Androidのそれぞれの動画+実装という形でまとめておきたいと思います。
別の画面に遷移したい
iOS | Android |
---|---|
たとえば、ルーム一覧画面からパスを"/rooms/<roomId>“とするルーム詳細画面(RoomScreen)に遷移したいときは次のようにします。1
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(name: "/rooms/<roomId>"),
builder: (BuildContext context) => RoomScreen(roomId)),
);
前の画面に戻りたい
iOS | Android |
---|---|
Navigator.push
を使って画面遷移したあと、左上のバックボタンをタップして戻る場合は特に処理は必要ありませんが、それ以外のボタンから戻りたい場合もあるかと思います。その場合は戻りたいタイミングで次のようにします。(上の動画では、下部のバツボタンをタップしています)
Navigator.pop(context);
ダイアログを出したい
iOS | Android |
---|---|
showDialogとAlertDialogの組み合わせです。AlertDialogの方のドキュメントにもサンプルが載っています。
Future<bool> showDialogMessage(BuildContext context,
{String title, String message, bool isOkOnly = false}) {
return showDialog<bool>(
context: context,
builder: (context) =>
_buildDialog(context, title, message, isOkOnly: isOkOnly),
);
}
Widget _buildDialog(BuildContext context, String title, String message,
{bool isOkOnly = false}) {
if (title == null && message == null) {
throw ArgumentError("titleとmessageのどちらともnullです。どちらかは指定してください。");
}
List<Widget> actions = List();
if (!isOkOnly) {
actions.add(FlatButton(
child: Text(“Cancel”),
onPressed: () {
Navigator.pop(context, false);
},
));
}
actions.add(FlatButton(
child: Text(“OK”),
onPressed: () {
Navigator.pop(context, true);
},
));
return AlertDialog(
title: title != null ? Text(title) : null,
content: message != null ? Text(message) : null,
actions: actions,
);
}
前の画面にデータを渡したい
AndroidのonActivityResult的なことをしたいわけですが、まずawaitを付けて別の画面に遷移します。
void _showProfileEditPage(BuildContext context) async {
User user = await Navigator.push(/* 省略 */).;
if (user == null) {
// something
return;
}
// something
}
そして、遷移した先の前の画面にデータを渡したいタイミングでNavigator.of(context).pop(_user)
とすると前の画面にデータを渡せます。
この例ではUser型の_userを返してます。
前の画面に戻る前に、戻っていいか確認ダイアログを出しOKの場合のみ戻りたい
iOS | Android |
---|---|
編集画面などでなにか編集して確定する前に間違えてとかで前に戻ろうとしてしまう場合があるかと思います。 そのようなときは、編集中ですが戻っていいか確認ダイアログを出すこともあると思います。
上の動画は、ダイアログを出したあと「Cancel」をタップしたらなにもせず、「OK」をタップしたら前の画面に戻る動画になります。
これをするには、WillPopScope
でWrapします。
WillPopScopeのonWillPop
はバックキーイベントを検知してくれるような動作になるので、そこでダイアログを出したりするとよさそうです。
Widget body = WillPopScope(
onWillPop: _willPopCallback,
child: Form(
key: _formKey,
child: SafeArea(
child: Container(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
roomImageWidget,
Expanded(
child: TextFormField(
controller: _controller,
onSaved: (String name) {
debugPrint(name);
_editingRoom.name = name;
},
),
)
],
),
),
)),
);
onWillPop
の_willPopCallback
は下記のようにするとできます。
Future<bool> _willPopCallback() async {
if (_textEditController.text != <画面遷移時にTextFieldにセットした値>) {
return showDialogMessage(context,
title: <タイトル>,
message: <メッセージ>) ??
false;
}
// trueで戻る。falseで戻らない
return true;
}
showDialogMessage
は先述した「ダイアログを出したい」で紹介した自前のメソッドです。
モーダルで遷移したい
iOS | Android |
---|---|
左上のボタンをバックボタン(← or <)はなくバツボタン(x)にするには、MaterialPageRoute
にfullscreenDialog
プロパティがある2ので、それをtrue
にするだけです。
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(name: "/rooms/<roomId>/edit"),
builder: (BuildContext context) => RoomScreen(roomId),
fullscreenDialog: true,
),
);
今までの画面を破棄して新しく画面を表示したい
iOS | Android |
---|---|
上の動画はサインイン後にサインイン画面は不要なので破棄しホーム画面に遷移しています。左上にバックボタンやバツボタンがないことがわかるかと思います。
これを実現するにはpushReplacementを使います。
Navigator.of(context).pushReplacement(
MaterialPageRoute(
settings: RouteSettings(name: "/home"),
builder: (BuildContext context) => HomeScreen(),
),
);
BottomSheetで選択させたい
iOS | Android |
---|---|
showModalBottomSheetを使います。3
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) => Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.photo_library),
title: Text("Gallery"),
onTap: onTapGallery,
),
ListTile(
leading: Icon(Icons.camera_alt),
title: Text("Camera"),
onTap: onTapCamera,
),
],
));
SnackBarを表示したい
iOS | Android |
---|---|
Scaffold.of(context).showSnackBar(SnackBar(content: Text(text)));
画面遷移時にアニメーションしたい
iOS | Android |
---|---|
MaterialPageRoute
を継承したクラスを作って、buildTransitions
メソッド内でアニメーションさせたいTransitionウィジェットを返すようにします。4
そして、上で紹介したMaterialPageRoute
の部分を継承先のクラスに置き換えてやれば画面遷移時にアニメーションします。
class _FadeAnimationCustomRoute<T> extends MaterialPageRoute<T> {
_FadeAnimationCustomRoute(
{WidgetBuilder builder, RouteSettings settings, bool fullscreenDialog = false})
: super(
builder: builder,
settings: settings,
fullscreenDialog: fullscreenDialog);
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
if (settings.isInitialRoute) return child;
return FadeTransition(opacity: animation, child: child);
}
}
上の動画はこれを実装したものですが、フェードしてるのわかりますかね😅
おわりに
以上、実際に作成したアプリで必要だった画面遷移に関してまとめてみました。 ご参考になれば幸いです。