システム開発部のTです。
前回、Flutterの勉強ということで、画面テーマ〜レイアウト作成まで掲載したかと思います。今回は、Flutterでの基本的な画面遷移の実装と、簡単な画面遷移のアニメーションについて書いていきたいと思います。
以前の記事はFlutter掲載シリーズを参照ください。
準備
プロジェクトを新規で作成後、最初から作成済みのmain.dartを開いてください。main()関数を先頭に、MyAppクラス、MyHomePageクラスと続いているかと思います。
とりあえず、MyHomePageクラス以降をすべて削除し、main()関数、MyAppクラスのみ残すようにコードを編集してください。
最終的には、以下のみになります。
main.dart
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
ここまで出来たところで、遷移用の画面を準備します。
本件では、FirstPage、NextPageの2画面を用意します。
FirstPage.dart
import 'package:flutter/material.dart';
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FirstPage'),
centerTitle: true,
),
body: Container(
padding: EdgeInsets.all(32.0),
child: Center(
child: Column(
children: <Widget>[
RaisedButton(onPressed: () => {}, child: Text('Nextページへ'),)
],
),
),
),
);
}
}
NextPage.dart
import 'package:flutter/material.dart';
class NextPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('NextPage'),
centerTitle: true,
),
body: Container(
padding: EdgeInsets.all(32.0),
child: Center(
child: Column(
children: <Widget>[
RaisedButton(onPressed: () => {}, child: Text('Firstページに戻る'),)
],
),
),
),
);
}
}
ここまでが準備になります。
以降、上記のコードを踏まえて画面遷移の実装を進めていきたいと思います。
画面遷移の実装
画面遷移の実装に入りますが、まずmain.dartを開き、以下のhomeの定義を変更してください。これで初期画面としてFirstPageが開きます。
// home: MyHomePage(title: 'Flutter Demo Home Page'),
home: FirstPage(),
次にFirstPage.dartを開き、RaisedButtonのonPressedに画面遷移するためのコードを記述しましょう。
Navigator.push(
context,
MaterialPageRoute(builder: (context)=>NextPage(),)
)
上記を追加し、最終的に以下のコードになるかと思います。
FirstPage.dart
import 'package:flutter/material.dart';
import 'package:quiz_app/NextPage.dart';
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FirstPage'),
centerTitle: true,
),
body: Container(
padding: EdgeInsets.all(32.0),
child: Center(
child: Column(
children: <Widget>[
RaisedButton(onPressed: () => {
Navigator.push(
context,
MaterialPageRoute(builder: (context)=>NextPage(),)
)
}, child: Text('Nextページへ'),)
],
),
),
),
);
}
}
上記のように実装後、アプリを起動するとFirstPageが表示され、「Nextページへ」ボタンを押下することで、NextPageが表示されるかと思います。
NextPage表示後、OSの戻るボタンを押下することで、呼び出し元のFirstPageに戻ることを確認してください。
画像はiOS、Androidそれぞれの画面結果です。


Navigatorについて
Navigatorは画面遷移するときに使用します。
次画面に遷移するときは、Navigator.push()メソッドを使います。
Navigator.push(
context,
MaterialPageRoute(builder: (context)=>NextPage(),)
)
ここで、pushメソッドの第2引数に遷移先を指定しますが、遷移先はMaterialPageRouteでインスタンス化したものを定義する必要があります。
逆に、遷移先から遷移元に戻る場合は、Navigator.pop()を利用することで、
意図したタイミングで戻ることが可能となります。
Navigator.pop(context)
また、呼び出し元が存在するかの確認は、以下のNavigator.canPop()で判定可能です。
Navigator.canPop(context)
上記にて、呼び出し元が存在しない場合は、以下でアプリを終了することができます(Androidのみ)。
SystemNavigator.pop()
上記をNextPageの「Firstページに戻る」ボタン押下時のイベント処理に設定します。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class NextPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('NextPage'),
centerTitle: true,
),
body: Container(
padding: EdgeInsets.all(32.0),
child: Center(
child: Column(
children: <Widget>[
RaisedButton(onPressed: () => {
if (Navigator.canPop(context)) { // 呼び出し元が存在するか?
Navigator.pop(context) // 呼び出し元に戻る
} else {
SystemNavigator.pop() // アプリを終了する
}
}, child: Text('Firstページに戻る'),),
],
),
),
),
);
}
}
ここまでで再度アプリを実行してみましょう。

いかがでしょうか。「Firstページに戻る」ボタンをタップすることで、FirstPageに戻れたかと思います。OSの戻るボタンでも、もちろん呼び出し元の画面に戻れますが、意図的に実装したい場合は、上記のpopを利用することが可能です。
push、popときて、感のいい人であれば分かるかと思いますが、
Flutterの画面遷移の管理はStackでの管理となっています。
後に入れたものを先に出す(後入れ先出し)ので、次の画面に遷移し、一つ前の画面に戻るが基本となります。
では、呼び出し元に戻りたくない場合や、複数ページに遷移したあとに一気に最初のページに戻る場合のパターンはどうするか?そういうのも用意されています。
画面を入れ替える
画面入れ替えの処理でNavigator.pushReplacement()というのがございます。
Navigator.push()と同じように画面遷移しますが、pushReplacementの名前にもあるように画面を入れ替えます。つまり、Stackでいう「入れる」ではないため、現在の画面と入れ替える処理になります。つまり元の画面はpopで戻れなくなります。
例として、先程作成したFirstPageに画面を入れ替える処理を追加してみます。
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FirstPage'),
centerTitle: true,
),
body: Container(
padding: EdgeInsets.all(32.0),
child: Center(
child: Column(
children: <Widget>[
RaisedButton(onPressed: () => {
Navigator.push(
context,
MaterialPageRoute(builder: (context)=>NextPage(),)
)
}, child: Text('Nextページへ(戻れる)'),),
RaisedButton(onPressed: () => { // ここのボタンに入替処理を追加
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context)=>NextPage(),)
)
}, child: Text('Nextページへ(戻れない)'),),
],
),
),
),
);
}
}
実行してみます。

いかがでしょう。
こんな感じに戻れなくなっているのが分かるかと思います。
基本的な画面遷移はpushですが、必要に応じてpushReplacementも利用してみてください。
ルーティングをつかった画面遷移
MaterialAppにあらかじめルーティングの設定をすることで、画面パスを設定して画面遷移をすることができるようになります。
とりあえず例を。
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: '/', // ここ以降の定義がルーティング定義
routes: {
'/': (context) => FirstPage(),
'/next': (context) => NextPage(),
},
// home: FirstPage(), ルーティング定義時、homeは削除する
);
}
}
上記のように、routesに画面パスと画面クラスを紐付けるように定義しておきます。気をつける点として、homeは削除しておきましょう。
このように定義し、以下で呼び出すことができます。
Navigator.pushNamed(context, '/next') // 画面を呼び出す
Navigator.pushReplacementNamed(context, '/next') // 画面を入れ替える
上記を踏まえて、先程のFirstPageのコードを置き換えると・・・、
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FirstPage'),
centerTitle: true,
),
body: Container(
padding: EdgeInsets.all(32.0),
child: Center(
child: Column(
children: <Widget>[
RaisedButton(onPressed: () => {
Navigator.pushNamed(context, '/next')
}, child: Text('Nextページへ(戻れる)'),),
RaisedButton(onPressed: () => {
Navigator.pushReplacementNamed(context, '/next')
}, child: Text('Nextページへ(戻れない)'),),
],
),
),
),
);
}
}
どうでしょう。
同じ処理でも、実装がシンプルになりました。
あらためて画面の動きも見てみましょう!
左がAndroid、右がiOSです。
同じように画面遷移しているのが分かるかと思います。
また、戻るときの遷移についても、今までどおりNavigator.pop()を利用することも可能ですが、以下のNavigator.popUntil()を利用することで、パスを直接指定し、その画面まで一気に戻ることも可能になります。
Navigator.popUntil(context, ModalRoute.withName('/'))
これなら、最初の画面から何度も画面遷移した場合にも、楽に実装できますね。
個人的には、最初にルーティングを決めたあとに、画面の実装を進めたほうが、実装も捗るのではと思っています。
画面遷移のアニメーション
ここまで実装していって、お気づきになった方もいるのでは?と思っていますが、AndroidとiOSで画面遷移のアニメーションが異なっていますよね。AndroidとiOSでは、基本的なUIの考え方が違うので、アニメーションが異なることについては問題ございません。
しかし、どちらかに寄せたいとは、カスタムしたいとかっていう要望ってあったりするかと思います。
最後に、このアニメーションについてお話できればと思います。
まず、どこでアニメーションの定義ができるかというと、MaterialAppのthemeで出来ます。themeを設定するために利用するThemeDataにpageTransitionsThemeがあるので、そこに定義することでアニメーションを変更することが可能です。でも、その前にデフォルトでは何が設定されているのかをコードを追ってみてみましょう。以下がそのテーマになります。
@immutable
class PageTransitionsTheme with Diagnosticable {
/// Construct a PageTransitionsTheme.
///
/// By default the list of builders is: [FadeUpwardsPageTransitionsBuilder]
/// for [TargetPlatform.android], and [CupertinoPageTransitionsBuilder] for
/// [TargetPlatform.iOS] and [TargetPlatform.macOS].
const PageTransitionsTheme({ Map<TargetPlatform, PageTransitionsBuilder> builders }) : _builders = builders;
static const Map<TargetPlatform, PageTransitionsBuilder> _defaultBuilders = <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.linux: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.windows: FadeUpwardsPageTransitionsBuilder(),
};
TargetPlatform.androidとか、TargetPlatform.iOSとか・・・、この辺にデフォルトの画面遷移の内容があります。動きをカスタマイズしたいので、一旦上記の定義を直接pageTransitionsThemeに当て込んでみましょう。
コード的には以下の内容になりますね。
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
pageTransitionsTheme: const PageTransitionsTheme( //ここを追加
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
},
),
),
initialRoute: '/',
routes: {
'/': (context) => FirstPage(),
'/next': (context) => NextPage(),
},
);
}
}
試しにAndroidをiOS風に画面アニメーションを変更してみます。
その場合、TargetPlatform.androidにiOSと同様「CupertinoPageTransitionsBuilder()」を定義しましょう。
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: CupertinoPageTransitionsBuilder(),//←変更
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
},
),
ここまで実装したら、Androidで再確認してみましょう!
iOSと同じ動きになったかと思います。

上記を利用することで、iOSをAndroid風に設定することも可能になります。
MaterialAppで定義することで、画面遷移のアニメーションを変更できることは理解できたかと思います。
本件ではここまでとしますが、各画面遷移ごとにアニメーションを変更することも可能です。その実装手段については、後日記載していければと思います。
まとめ
本件では、画面遷移を中心に、基本的な画面遷移時の実装、アニメーションについてお話しました。最後に復習していきます。
- Navigatorでの画面遷移
画面遷移はStack(後入れ先出し)管理されている。
画面遷移は基本的に進むはpush、戻るはpopを利用する
画面の入れ替えは、pushReplacementで可能 - ルーティング定義
MaterialAppのroutesにパス名と画面クラスの紐付けを定義することで、画面遷移時にパス名に紐付けた画面を呼び出すことが可能。
popするときも、popUntilを利用することで、直接その画面に戻ることが可能となり、何度もpopする必要がなくなる - 画面遷移アニメーション
MaterialAppのtheme内のパラメータpageTransitionsThemeに対してアプリ全体の画面遷移アニメーションを定義することが可能
AndroidをiOS風に、iOSをAndroid風に切り替えが可能
以上になりますが、画面遷移についてはまだまだ話足りないこともあるので、
次回も引き続き画面遷移の話ができればと思います。