FlutterでPageViewにPageIndicatorを実装してみた

# よくあるPageViewを実装してみたかった。

↓こういうやつ
<img width="228" alt="スクリーンショット 2019-11-06 13.44.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514870/c4d0c07e-ec8c-fdd7-88ed-4d6c1128ca29.png">

一番最後にはIndicatorをボタンに変化させてよくある説明ページみたいなのを実装しています。

今回使用したライブラリーは以下の二つです。

メインのIndicator↓
https://pub.dev/packages/flutter_page_indicator

Indicatorのアニメーション↓
https://pub.dev/packages/transformer_page_view

# パッケージの準備

## パッケージのインストール
```pubspec.yaml
dependencies:
  flutter_page_indicator: ^0.0.3
  transformer_page_view: ^0.1.6
```
上記を記載後
packages getを実行


## パッケージのインポート
```main.dart
import 'package:flutter_page_indicator/flutter_page_indicator.dart';
import "package:transformer_page_view/transformer_page_view.dart";
```

# 実装

## 全体コード

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

import 'package:flutter_page_indicator/flutter_page_indicator.dart';

import "package:transformer_page_view/transformer_page_view.dart";


class MyAppView extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MyHomePage();
  }

}

class MyHomePage extends StatefulWidget {

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

class _MyHomePageState extends State<MyHomePage> {
  double size = 20.0;
  double activeSize = 30.0;

  PageController controller;

  bool loop = false;

  bool _visible1 = true;
  var page_count = 1;

  void pageNumberChange(page) {
    setState(() {
      page_count = page + 1;
    });
  }

  @override
  void initState() {
    controller = new PageController();
    super.initState();
  }

  @override
  void didUpdateWidget(MyHomePage oldWidget) {

    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    var children = <Widget>[
      new Column(
        children: <Widget>[
          Container(
            height: 150,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  "アプリの説明1アプリの説明1",
                  style: TextStyle(
                      fontSize: 24.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
          Container(
            width: 300,
            height: 300,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                SizedBox(
                  width: 300,
                  height: 200,
                  child: FlatButton(
                    color: Colors.black54,
                    onPressed: () {
                      Navigator.pushNamed(context, '/test');
                    },
                    child: Text("1枚目"),
                  ),
                ),
                Text(
                  "アプリの説明1アプリの説明1アプリの説明1アプリの説明1アプリの説明1",
                  style: TextStyle(
                      fontSize: 14.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
      new Column(
        children: <Widget>[
          Container(
            height: 150,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  "アプリの説明2アプリの説明2",
                  style: TextStyle(
                      fontSize: 24.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
          Container(
            width: 300,
            height: 300,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                SizedBox(
                  width: 300,
                  height: 200,
                  child: FlatButton(
                    color: Colors.black54,
                    onPressed: () {
                      Navigator.pushNamed(context, '/test');
                    },
                    child: Text("2枚目"),
                  ),
                ),
                Text(
                  "アプリの説明2アプリの説明2アプリの説明2アプリの説明2アプリの説明2",
                  style: TextStyle(
                      fontSize: 14.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
      new Column(
        children: <Widget>[
          Container(
            height: 150,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  "アプリの説明3アプリの説明3",
                  style: TextStyle(
                      fontSize: 24.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
          Container(
            width: 300,
            height: 300,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                SizedBox(
                  width: 300,
                  height: 200,
                  child: FlatButton(
                    color: Colors.black54,
                    onPressed: () {
                    },
                    child: Text("3枚目"),
                  ),
                ),
                Text(
                  "アプリの説明3アプリの説明3アプリの説明3アプリの説明3アプリの説明3",
                  style: TextStyle(
                      fontSize: 14.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
        ],
      ),new Column(
        children: <Widget>[
          Container(
            height: 150,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  "アプリの説明4アプリの説明4",
                  style: TextStyle(
                      fontSize: 24.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
          Container(
            width: 300,
            height: 300,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                SizedBox(
                  width: 300,
                  height: 200,
                  child: FlatButton(
                    color: Colors.black54,
                    onPressed: () {
                    },
                    child: Text("4枚目"),
                  ),
                ),
                Text(
                  "アプリの説明4アプリの説明4アプリの説明4アプリの説明4アプリの説明4",
                  style: TextStyle(
                      fontSize: 14.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    ];

    //Widgetを生成

    return new Scaffold(
        appBar: new AppBar(
          title: new Text("アプリ説明ページ"),
        ),
        body: new Column(
          children: <Widget>[
            new Expanded(
                child: new Stack(
                  children: <Widget>[
                    loop ? new TransformerPageView.children(
                      children: children,
                      pageController: controller,
                    ):
                    new PageView(
                      onPageChanged: (page_count){
                        pageNumberChange(page_count);
                        if(page_count + 1 == children.length){
                          _visible1 = false;
                        }else{
                          _visible1 = true;
                        }
                      },
                      controller: controller,
                      children:children ,
                    ),
                    new Align(
                      alignment: Alignment.bottomCenter,
                      child: new Padding(
                        padding: new EdgeInsets.only(bottom: 20.0),
                        child: new Stack(
                          children: <Widget>[
                            Align(
                              alignment: Alignment.bottomCenter,
                              child: new Padding(
                                  padding: new EdgeInsets.only(bottom: 20.0),
                                  child: AnimatedOpacity(
                                    opacity: _visible1 ? 1.0 : 0.0,
                                    duration: Duration(milliseconds: 500),
                                    child: PageIndicator(
                                      layout: PageIndicatorLayout.NONE,
                                      size: size,
                                      activeSize:activeSize,
                                      controller: controller,
                                      space: 20,
                                      count: 4,
                                      color: Colors.black,
                                      activeColor: Colors.red,
                                    ),
                                  ),
                              )
                            ),
                            Align(
                              alignment: Alignment.bottomCenter,
                              child: new Padding(
                                  padding: new EdgeInsets.only(bottom:  20.0),
                                  child: AnimatedOpacity(
                                    opacity: _visible1 ? 0.0 : 1.0,
                                    duration: Duration(milliseconds: 500),
                                    child:SizedBox(
                                      width: 300,
                                      height: 50,
                                      child: RaisedButton(
                                        color: Colors.blue,
                                        onPressed: () {
                                          Navigator.pushNamed(
                                              context,
                                              '/hello'
                                          );
                                        },
                                        shape: RoundedRectangleBorder(
                                            borderRadius: BorderRadius.all(
                                                Radius.circular(5.0))
                                        ),
                                        child: Text(
                                          "はじめる",
                                          style: TextStyle(
                                              fontSize: 20.0,
                                              color: Colors.white
                                          ),
                                        ),
                                      ),
                                    ),
                                  ),
                              ),
                            )
                          ],
                        )
                      ),
                    )
                  ],
                )
            )
          ],
        )
    );
  }
}

```
## 部分別の解説

### ①説明ページのWidgetを配列にそ追加

`children`に４つの説明ページを入れています。
(ここほんとはメソッドでループ回したい…)


```main.dart
//省略
@override
  Widget build(BuildContext context) {
    var children = <Widget>[
      new Column(
        children: <Widget>[
          Container(
            height: 150,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  "アプリの説明1アプリの説明1",
                  style: TextStyle(
                      fontSize: 24.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
          Container(
            width: 300,
            height: 300,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                SizedBox(
                  width: 300,
                  height: 200,
                  child: FlatButton(
                    color: Colors.black54,
                    onPressed: () {
                      Navigator.pushNamed(context, '/test');
                    },
                    child: Text("1枚目"),
                  ),
                ),
                Text(
                  "アプリの説明1アプリの説明1アプリの説明1アプリの説明1アプリの説明1",
                  style: TextStyle(
                      fontSize: 14.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
      new Column(
        children: <Widget>[
          Container(
            height: 150,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  "アプリの説明2アプリの説明2",
                  style: TextStyle(
                      fontSize: 24.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
          Container(
            width: 300,
            height: 300,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                SizedBox(
                  width: 300,
                  height: 200,
                  child: FlatButton(
                    color: Colors.black54,
                    onPressed: () {
                      Navigator.pushNamed(context, '/test');
                    },
                    child: Text("2枚目"),
                  ),
                ),
                Text(
                  "アプリの説明2アプリの説明2アプリの説明2アプリの説明2アプリの説明2",
                  style: TextStyle(
                      fontSize: 14.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
      new Column(
        children: <Widget>[
          Container(
            height: 150,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  "アプリの説明3アプリの説明3",
                  style: TextStyle(
                      fontSize: 24.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
          Container(
            width: 300,
            height: 300,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                SizedBox(
                  width: 300,
                  height: 200,
                  child: FlatButton(
                    color: Colors.black54,
                    onPressed: () {
                    },
                    child: Text("3枚目"),
                  ),
                ),
                Text(
                  "アプリの説明3アプリの説明3アプリの説明3アプリの説明3アプリの説明3",
                  style: TextStyle(
                      fontSize: 14.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
        ],
      ),new Column(
        children: <Widget>[
          Container(
            height: 150,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  "アプリの説明4アプリの説明4",
                  style: TextStyle(
                      fontSize: 24.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
          Container(
            width: 300,
            height: 300,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                SizedBox(
                  width: 300,
                  height: 200,
                  child: FlatButton(
                    color: Colors.black54,
                    onPressed: () {
                    },
                    child: Text("4枚目"),
                  ),
                ),
                Text(
                  "アプリの説明4アプリの説明4アプリの説明4アプリの説明4アプリの説明4",
                  style: TextStyle(
                      fontSize: 14.0,
                      color: Colors.black54
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    ];
```

### ②説明ページの配置と、下部にIndicator、ボタンの配置
`PageIndicator`と`FlatButton`を`Stack構造`にして、
`PageView`の`onPageChanged`メソッドで最終ページにスワイプ時
アニメーションでボタンに切り替えるようにしています。

```main.dart
return new Scaffold(
        appBar: new AppBar(
          title: new Text("アプリ説明ページ"),
        ),
        body: new Column(
          children: <Widget>[
            new Expanded(
                child: new Stack(
                  children: <Widget>[
                    loop ? new TransformerPageView.children(
                      children: children,
                      pageController: controller,
                    ):
                    new PageView(
                      onPageChanged: (page_count){
                        pageNumberChange(page_count);
                        if(page_count + 1 == children.length){
                          _visible1 = false;
                        }else{
                          _visible1 = true;
                        }
                      },
                      controller: controller,
                      children:children ,
                    ),
                    new Align(
                      alignment: Alignment.bottomCenter,
                      child: new Padding(
                        padding: new EdgeInsets.only(bottom: 20.0),
                        child: new Stack(
                          children: <Widget>[
                            Align(
                              alignment: Alignment.bottomCenter,
                              child: new Padding(
                                  padding: new EdgeInsets.only(bottom: 20.0),
                                  child: AnimatedOpacity(
                                    opacity: _visible1 ? 1.0 : 0.0,
                                    duration: Duration(milliseconds: 500),
                                    child: PageIndicator(
                                      layout: PageIndicatorLayout.NONE,
                                      size: size,
                                      activeSize:activeSize,
                                      controller: controller,
                                      space: 20,
                                      count: 4,
                                      color: Colors.black,
                                      activeColor: Colors.red,
                                    ),
                                  ),
                              )
                            ),
                            Align(
                              alignment: Alignment.bottomCenter,
                              child: new Padding(
                                  padding: new EdgeInsets.only(bottom:  20.0),
                                  child: AnimatedOpacity(
                                    opacity: _visible1 ? 0.0 : 1.0,
                                    duration: Duration(milliseconds: 500),
                                    child:SizedBox(
                                      width: 300,
                                      height: 50,
                                      child: RaisedButton(
                                        color: Colors.blue,
                                        onPressed: () {
                                          Navigator.pushNamed(
                                              context,
                                              '/hello'
                                          );
                                        },
                                        shape: RoundedRectangleBorder(
                                            borderRadius: BorderRadius.all(
                                                Radius.circular(5.0))
                                        ),
                                        child: Text(
                                          "はじめる",
                                          style: TextStyle(
                                              fontSize: 20.0,
                                              color: Colors.white
                                          ),
                                        ),
                                      ),
                                    ),
                                  ),
                              ),
                            )
                          ],
                        )
                      ),
                    )
                  ],
                )
            )
          ],
        )
    );
```
# 完成したもの
![simulator-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514870/344349e1-de69-7fef-4756-bb41ba67f39e.gif)

ボタンの遷移のところは、
`PageIndicator`の`layout`を変更すればいろんなアニメーションになります。
詳しくはパッケージのドキュメントを参照してください！

# 最後に
ネストがすごい深くなってしまったので反省しています。
`children`のところとか他の部分もいろいろ直せる部分多いと思うので、
ぜひご指摘ください。


Flutter楽しいです。