Flutterの画面遷移の基本

---
# :book: Flutterの記事を整理し本にしました :book:
https://zenn.dev/kazutxt/books/flutter_practice_introduction

* <font color="red"><b>本稿の記事を含む様々な記事を体系的に整理し本にまとめました</b></font>
* 今後はこちらを最新化するため、<font color="red"><b>最新情報はこちらをご確認くださ</b>い</font>
* 20万文字を超える超大作になっています!!

---

# はじめに
* Flutterの画面遷移を整理しました
* Navigator2.0についても整理しようと思ったのですが、複雑すぎる＆あまり実用的なレベルで使われていない？っぽいので、諦めました :sweat: 

## まとめ
* [Flutter系の記事のまとめ](https://qiita.com/kazutxt/items/524acb1c134015682ccf)

# Flutterのページ遷移

Flutterで画面を遷移させる方法は大きく2種類あります。
(もちろん工夫次第で無限の可能性がありますが)

# 1. Navigator
Navigatorを使うことで、ページ遷移を実現できます。
Webサイトのような一般的なリンクによる次のページへの遷移と、戻るのようなイメージで実現ができます。

現在は、Navigator2.0という次のバージョンもありますが、機能的には問題なく1.0も使えますので、両方紹介します。

## Navigator1.0

![pic24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/d15793c2-b0f2-fc95-4329-34e178dc30f1.png)



```dart:main.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hello_world/TestPage1.dart';
void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(body: TestPage1());
  }
}
```

```dart:TestPage1.dart
import 'package:flutter/material.dart';
import 'package:hello_world/TestPage2.dart';
class TestPage1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Test1"),
        ),
        body: Center(
            child: TextButton(
                onPressed: () => {
                      Navigator.of(context)
                          .push(MaterialPageRoute(builder: (context) {
                        return TestPage2();
                      }))
                    },
                child: Text("進む", style: TextStyle(fontSize: 80)))));
  }
}
```

```dart:TestPage2.dart
import 'package:flutter/material.dart';
import 'package:hello_world/TestPage3.dart';
class TestPage2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Test2"),
        ),
        body: Center(
            child:
                Column(mainAxisAlignment: MainAxisAlignment.center, children: [
          TextButton(
              onPressed: () => {
                    Navigator.of(context)
                        .push(MaterialPageRoute(builder: (context) {
                      return TestPage3();
                    }))
                  },
              child: Text("進む", style: TextStyle(fontSize: 80))),
          TextButton(
              onPressed: () => {Navigator.of(context).pop()},
              child: Text("戻る", style: TextStyle(fontSize: 80)))
        ])));
  }
}
```

```dart:TestPage3.dart
import 'package:flutter/material.dart';
class TestPage3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Test3"),
        ),
        body: Center(
            child: TextButton(
                onPressed: () => {Navigator.of(context).pop()},
                child: Text("戻る", style: TextStyle(fontSize: 80)))));
  }
}
```
![pic22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/953985c5-c421-c1d3-9c86-4b313faf4fd5.png)


mainからは単純にTestPage1を呼び出し、ほぼ同じ構造を持ったTestPage1,2,3を進むと戻るをpushとpopで実現しています。

### Route

直接Widgetを指定する以外に、名前を付けておいて、その名前でルーティングする方法もあります。

```dart:main.dart
//中略
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
+      routes: {
+        "/test1": (BuildContext context) => TestPage1(),
+        "/test2": (BuildContext context) => TestPage2(),
+        "/test3": (BuildContext context) => TestPage3(),
+      },
    );
  }
}
// 中略
```

```dart:TestPage1.dart
// 中略
child: TextButton(
   onPressed: () => {
+      Navigator.of(context).pushNamed("/test2")
+      // 下記の書き方でも可
+      // Navigator.pushNamed(context, "/test2")
-      // MaterialPageRoute(builder: (context) {
-      //   return TestPage2();
-      // },
      child: Text("進む", style: TextStyle(fontSize: 80)))));
}
// 中略
```

## Navigator2.0

Navigatorには、2.0というバージョンも存在します。
Navigatro1.0では、pushとpopでスタック構造で管理するため、柔軟度にかけるという意見から、List構造で管理し、Stackのように階層的に表示しているとみなして、一番最後の要素が最も上にある画面として表示されるという構造です。

ただし、2021年4月時点で、十分に議論された開発に実用的なレベルにはなっていない状況のようなので、ここでは解説は割愛いたします。

# 2. PageView
Navigatorを使わずにページ遷移相当のことを実現する方法もあります。
ここでは、PageViewを使った実装例を紹介します。


![pic23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/db23ad79-756f-cf08-5d0a-76a1f97879a7.png)



1つの画面の中に3つのページを内包しており、そのページを切り替えるというやり方になります。

```dart:main.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hello_world/TestPage1.dart';
import 'package:hello_world/TestPage2.dart';
import 'package:hello_world/TestPage3.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late PageController _pageController;
  int _selectedIndex = 0;

  // ボトムメニューの遷移先画面
  var _pages = [
    TestPage1(),
    TestPage2(),
    TestPage3(),
  ];
  @override
  void initState() {
    super.initState();
    _pageController = PageController(initialPage: _selectedIndex);
  }

  @override
  void dispose() {
    super.dispose();
    _pageController.dispose();
  }

  void _onPageChanged(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    //return LoginPage();

    return Scaffold(
        body: PageView(
            controller: _pageController,
            onPageChanged: _onPageChanged,
            children: _pages));
  }
}
```

```dart:TestPage1.dart
import 'package:flutter/material.dart';

class TestPage1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Test1"),
        ),
        body: Center(
            child: Container(
                color: Colors.redAccent,
                child: Text("Test1", style: TextStyle(fontSize: 80)))));
  }
}
```

```dart:TestPage2.dart
import 'package:flutter/material.dart';
class TestPage2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Test2"),
        ),
        body: Center(
            child: Container(
                color: Colors.greenAccent,
                child: Text("Test2", style: TextStyle(fontSize: 80)))));
  }
}
```

```dart:TestPage3.dart
import 'package:flutter/material.dart';
class TestPage3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Test3"),
        ),
        body: Center(
            child: Container(
                color: Colors.greenAccent,
                child: Text("Test3", style: TextStyle(fontSize: 80)))));
  }
}
```

![pic31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/aa5c742b-7071-cc2d-b0dc-19b669912991.png)


MyHomePageのなかで、PageViewを作り、その中に3つのTestPageを持っています。
画面の移動などはPageViewControllerが司っています。

スワイプでも移動できますが、Controllerを使ってアイコンクリックなどで直接該当ページを表示することも可能です。