Flutter - PopupMenuButtonでページ遷移機能を実装

## はじめに
Flutterで[PopupMenuButton](https://api.flutter.dev/flutter/material/PopupMenuButton-class.html)を使ってページ遷移機能を実装します。
次図のように、PopupMenuButtonで選択することでページ画面を遷移させるようにします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490241/229afe6d-28dd-4e3b-11cb-569ba1cb4a4c.png" width=75%>

## 実装

- lib配下のディレクトリ構成
次図のような構成で実装しています。
|-- lib 
| &nbsp;&nbsp; |-- main.dart
| &nbsp;&nbsp; |-- app_home.dart
| &nbsp;&nbsp; |-- pages
| &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; |-- page1.dart
| &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; |-- page2.dart

- ```lib/main.dart```
```StatefulWidget```の```AppHome```を呼び出しているだけです。

```dart
import 'package:flutter/material.dart';
import 'package:app_1/app_home.dart'; // プロジェクト名はapp_1としています

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),

      // StatefulWidgetであるAppHomeの呼び出し
      home: const AppHome(title: 'Demo: Page Transition'),
    );
  }
}
```

- ```lib/app_home.dart```
```_AppState```クラスにおいて、```PopupMenuButton```のクリック時の動作を定義する```onSelected```を利用して```_value```を切り替えています。
具体的には、```onSelected```に指定した```_select```関数で```setState```関数を呼び出し、```_value```を切り替えるとともに状態(State)をリビルドしています。（参考: [Stateのライフサイクルについて](https://api.flutter.dev/flutter/widgets/State-class.html)）
これにより、```Scaffold```の```body```が切り替わります。
また、```PopupMenuButton```に付しているページのアイコンは[こちら](https://fonts.google.com/icons?selected=Material+Icons)から選んでいます。

```dart
import 'package:flutter/material.dart';
import 'package:app_1/pages/page1.dart';
import 'package:app_1/pages/page2.dart';

// ページ情報（ページ番号、ページ名、アイコン）を定義するクラス
class MenuItem {
  int value;
  String name;
  IconData icon;
  MenuItem(
    this.value,
    this.name,
    this.icon
  );
}

// ページ情報のリストを定義
List<MenuItem> _list = <MenuItem>[
  MenuItem(1, 'Page 1', Icons.filter_1),
  MenuItem(2, 'Page 2', Icons.filter_2),
];

// AppHomeクラス（StatefulWidget）
class AppHome extends StatefulWidget {
  const AppHome({super.key, required this.title});
  final String title;

  @override
  State<AppHome> createState() => _AppState();
}

// _AppStateクラス（AppHomeの内部State）
class _AppState extends State<AppHome> {
  MenuItem _value = _list[0];
  bool favorite = false;
  void _select(MenuItem value) {
    // setStateを契機に状態がリビルドされる
    setState(() {
      _value = value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          actions: <Widget>[
            PopupMenuButton(
              initialValue: _value,
              icon: Icon(Icons.more_vert),
              enabled: true,
              // onSelectedで選択時の挙動を定義する
              onSelected: _select,
              itemBuilder: (context) {
                return _list.map((MenuItem choice) {
                  return PopupMenuItem(
                    value: choice,
                    child: ListTile(
                      leading: Icon(choice.icon),
                      title: Text(choice.name),
                    ),
                  );
                }).toList();
              }
            )
          ],
        ),
        body: PageWidget(),
      ),
    );
  }
  PageWidget() {
    if(_value.value == _list[0].value) {
      return Container(child: Page1(title: widget.title));
    }
    if(_value.value == _list[1].value) {
      return Container(child: Page2(title: widget.title));
    }
  }
}
```

- ```lib/pages/page1.dart```
ページ1を```StatelessWidget```で作成。

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

class Page1 extends StatelessWidget {
  const Page1({super.key, required this.title});
  final String title;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: this.title,
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        body: Center(
          child: Text(
            'Page 1',
            style: TextStyle(
              fontSize: 24,
              fontStyle: FontStyle.italic,
              color: Colors.blue,
            ),
            textAlign: TextAlign.center
          ),
        ),
      ),
    );
  }
}
```

- ```lib/pages/page2.dart```
ページ2を```StatelessWidget```で作成。

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

class Page2 extends StatelessWidget {
  const Page2({super.key, required this.title});
  final String title;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: this.title,
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        body: Center(
          child: Text(
            'Page 2',
            style: TextStyle(
              fontSize: 24,
              fontStyle: FontStyle.italic,
              color: Colors.red
            ),
            textAlign: TextAlign.center
          ),
        ),
      ),
    );
  }
}
```

## 動作環境
- WSL2(Ubuntu20.04 LTS)
- Flutter: 2.13.0
- Dart: 2.18.0
- DevTools: 2.12.2

## おわりに
Flutterは社内ハッカソンに参加した際のデモに使用し、興味を持ちました。
今後も勉強していきたいと思っています。

## 参考文献
  - [Flutter-study.dev](https://www.flutter-study.dev/introduction/about-flutter)