FlutterのPageView(スワイプとボタンでスクロール)

# はじめに
業務でPageViewを使ったので、備忘録代わりにまとめてみます。
Flutter初心者なので、間違ってる部分があったらご指摘ください。

# 参考
下記を参考にさせていただきました。
https://qiita.com/yoshua/items/741038cb8c8c8bf0e873

# 動作イメージ
![pageView.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/817871/580695a2-ec0e-5577-3f35-ef374568fd8b.gif)

PageViewのスワイプと、ボタンタップでスクロールするようにしました。

# 実装

ここは特にコメントはありません。

```
class TestScreen extends StatefulWidget {
  /// constructors
  const TestScreen({
    Key key,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => _TestScreenState();
}
```

initState時にPageControllerを用意します。
addListenerでスクロールの検知ができます。
あとで出てくる_MyPageView部分が、今回メインで作成したいPageViewです。
そのほか、RaisedButtonのonPressedでページ送り/戻しをしています。
todoで書いてありますが、_currentPageのmaxとminの制御が必要です
(動的に変化する場合も多いと思うので、ここでは割愛してます)。

```
class _TestScreenState extends State<TestScreen> {
  final _scaffoldKey = GlobalKey<ScaffoldState>();
  final _myPageViewKey = GlobalKey<_MyPageViewState>();

  PageController _pageController;
  int _currentPage = 0;

  @override
  void initState() {
    super.initState();

    _pageController = PageController();
    _pageController.addListener(() {
      final nextPage = _pageController.page.round();
      if (_currentPage != nextPage) {
        _currentPage = nextPage;
      }
    });
  }

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

  @override
  Widget build(BuildContext context) => Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: const Text('test'),
      ),
      backgroundColor: Colors.white,
      body: Builder(
        builder: (context) => SafeArea(
          child: Column(
            children: [
              _MyPageView(
                key: _myPageViewKey,
                pageController: _pageController,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  RaisedButton(
                    onPressed: () {
                      _currentPage--;  // todo: ページ数に応じた制御が必要
                      _pageController.animateToPage(_currentPage,
                          duration: const Duration(milliseconds: 300),
                          curve: Curves.easeInOut);
                    },
                    child: const Text('前へ'),
                  ),
                  RaisedButton(
                    onPressed: () {
                      _currentPage++;   // todo: ページ数に応じた制御が必要
                      _pageController.animateToPage(_currentPage,
                          duration: const Duration(milliseconds: 300),
                          curve: Curves.easeInOut);
                    },
                    child: const Text('次へ'),
                  ),
                ],
              ),
            ],
          ),
        ),
      ));
}
```

今回メインで作成したいPageViewです。

```
class _MyPageView extends StatefulWidget {
  /// constructors
  const _MyPageView({
    Key key,
    this.pageController,
  }) : super(key: key);

  final PageController pageController;

  @override
  State<StatefulWidget> createState() => _MyPageViewState();
}
```

PageView自体と、各ページを作成します。

```
class _MyPageViewState extends State<_MyPageView> {
  var _currentViewPage = 0;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) => Column(
        children: [
          _buildPageView(),
        ],
      );

  Widget _buildPageView() => ConstrainedBox(
        constraints: const BoxConstraints.expand(height: 100),
        child: PageView(
          onPageChanged: (page) {
            _currentViewPage = page;
          },
          controller: widget.pageController,
          children: List<Widget>.generate(
              10,   // ページ数 todo: 動的に変えるか、定数にする
              _buildPageViewChild),
        ),
      );

  // ページビュー各ページ
  Widget _buildPageViewChild(int pageIndex) => Row(
        children: [
          Expanded(
            child: Container(
              color: Colors.grey,
              child: Center(child: Text('$pageIndex')),
            ),
          )
        ],
      );
}
```