【Flutter】人気なパッケージの使い方 〜carousel_slider編〜

## まえがき
2020年入ってからFlutterの勉強を初めてきました。
今後いろんなパッケージをもっと勉強していきたいと思ってるので、人気なものをアウトプットとして記事にしていきたいと思います。

今回は**carousel_slider**と言うパッケージを紹介します。

これまで載せた記事
- [shared_preferencesの紹介](https://qiita.com/ryota47/items/bc381f85080dbf4b2454)
- [url_launcherの紹介](https://qiita.com/ryota47/items/0cd30257f063c06df1b8)
- [flutter_slidable](https://qiita.com/ryota47/items/6ed90ce011ea6aaa8698)
- [carousel_slider](https://qiita.com/ryota47/items/fb8e32f1a409026ba065)←いまこれ
## carousel_slider
carousel_sliderはいくつかのWidgetをスライドさせて表示できるようにするWidgetです。
画像などをスライドさせてみることができます。

手動でスライドするのと、自動でスライドするのを設定できたりもします。


## サンプル
`GridView`で並べられた画像をタップすると、その画像が拡大されて横にスライドさせると他の画像が表示できるようにしました。

![carousel_slider.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311390/46d75043-8044-d325-57d7-076c4c2d51f7.gif)


まずは何でもいいので画像を数枚用意してください。
imagesディレクトリを作り、
sample1.png
sample2.png
sample3.png
sample4.png
sample5.png
と名前の画像ファイルを入れました。
画像の表示の仕方は今回は説明してないので、わからない人は検索してみてください。


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

class ImageList extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _ImageList();
}

class _ImageList extends State<ImageList> {
  final List images = [
    "images/sample1.png",
    "images/sample2.png",
    "images/sample3.png",
    "images/sample4.png",
    "images/sample5.png",
    "images/sample6.png"
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('画像を表示'),
      ),
      body: GridView.builder(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
        ),
        itemCount: images.length,
        itemBuilder: (BuildContext context, int index) {
          return _photoItem(images[index], index);
        },
      ),
    );
  }

  Widget _photoItem(String image, int index) {
    return GestureDetector(
      child: Container(
        decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
        child: Image.asset(
          image,
          fit: BoxFit.cover,
        ),
      ),
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => SampleCarouselSlider(images: images, index: index),
            fullscreenDialog: true,
          ),
        );
      },
    );
  }
}

class SampleCarouselSlider extends StatefulWidget {
  final List images;
  final int index;

  SampleCarouselSlider({this.images, this.index});

  @override
  State<StatefulWidget> createState() => _SampleCarouselSlider(images, index);
}

class _SampleCarouselSlider extends State<SampleCarouselSlider> {
  List images;
  int index;

  _SampleCarouselSlider(this.images, this.index);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('carousel_slider'),
      ),
      body: CarouselSlider.builder(
        options: CarouselOptions(
          height: 600.0,
          initialPage: index,
          viewportFraction: 1,
          enableInfiniteScroll: false,
        ),
        itemCount: images.length,
        itemBuilder: (BuildContext context, int index) {
          return Image.asset(
            images[index],
          );
        },
      ),
    );
  }
}
```

## 説明
画像をタップすると、画像のパスが入ったリストとその画像の順番を引数で渡し`SampleCarouselSlider`と言う`StatefulWidget`が立ちます。

今回はFirestoreやFirebase Storageから画像を取得した時にも応用できるように`CarouselSlider.builder()`で作りました。
`itemCount`や`itemBuilder`は`ListView.builder()`と同じですね。

`CarouselOptions()`が重要で様々なプロパティがあります。
まずは今回のサンプルで使っているものです

- `height`：高さ
- `initialPage`：表示される最初のページ。本サンプルではindexでリストの番号を渡しているためタップした画像から始まっている
- `viewportFraction`：各ページが占めるビューポート(表示領域)の割合。小さくすると隣のページが見えるように画像が小さくなり、大きくすればするほど隣とのページが離れる。**1~0.8がおすすめ**
- `enableInfiniteScroll`：最後のページ⇄最初のページの遷移が可能かどうか。デフォルトは`true`

このようになっています。高さやビューポートの割合は、言葉の説明より、数字をいじってみることで理解した方が早いと思います。

ここからは他にも`CarouselOptions()`はこんなことができるよと言うのを紹介します。

- `aspectRatio`画面のアスペクト比。高さが指定されてない時に用いられる。デフォルトでは16/9
- `scrollDirection`：スクロールの向き`Axis.horizontal`で横、`Axis.vertical`で縦。デフォルトでは横向き
- `reverse`：ページの順番を逆にする。デフォルトは`false`
- `autoPlay`：自動でスライドしてくれるかどうか。デフォルトは`false`
- `autoPlayInterval`：自動スライドのインターバル時間、`Duration()`で指定。デフォルトは4秒。
- `autoPlayAnimationDuration`：自動スライドがが始まって終わるまでの時間。（スライドの速さ）`Duration`で指定。デフォルトで0.8秒(milliseconds: 100)と表記
- `autoPlayCurve`:自動スライドのアニメーションの違い。`Curves`で指定。たくさんあるので[Curvesの公式ドキュメント](https://api.flutter.dev/flutter/animation/Curves-class.html)でアニメーションの詳細を確認してください。デフォルトでは`Curves.fastOutSlowIn`

このような感じにいろいろ設定できます。

また`CarouselSlider.builder`ではなく実装するときはこんな感じにできます。
これをbodyの中に置き換えても問題ありません。


```dart
CarouselSlider(
  options: CarouselOptions(
    height: 600.0,
    initialPage: index,
    viewportFraction: 1,
    enableInfiniteScroll: false,
    reverse: false,
  ),
  items: images.map((i) {
    return Builder(
      builder: (BuildContext context) {
        return Image.asset(i);
      },
    );
  }).toList(),
)
```

## パッケージソースコードを読んだ感想
なんかすげい難しいことをしてるって感じではないのかな？って思った
アニメーション作るのは難しいかもしれないけど、
`CarouselOptions`クラスは変数定義して初期値入れてるだけ

`CarouselSlider`クラスは条件分岐がたくさんあって、訳わかんなくなるけどなんとなくわかる
自動スライドとか作ってるから複雑な感じになってるけど、ページを複数用意してスクロールさせるのはそんな難しくないのかもしれないと思いました。

あと一つ勉強になったのは

```dart
Widget wrapper;
if (widget.options.height != null) {
  wrapper = Container(height: widget.options.height, child: child);
} else {
  wrapper =
      AspectRatio(aspectRatio: widget.options.aspectRatio, child: child);
}
```

こんな感じにWidgetを変数で定義して条件分岐で中身変えてすぐしたの`child`で`wrapper`使ってたこと。すっきり見える
ちなみにこれは`CarouselOptions`で`height`が指定されてるかどうかの条件分岐ですね。