Flutterでループするスライダーを実装する（carousel_slider）


## 今回作るもの

Webでは過去に無限にスクロールできるカルーセルスライダーを実装したことがあったのですが、Flutterでどうやるんだろう？と思い調べてみました。

（いらすとやから、最近流行りの「ヤツ」を使ってみました）

| トップ画面 | ボトムシート | 動画 |
|----|----|----|
|   <img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/735195/ceda9d77-5acf-c8f3-b089-91d8cd6cd5f2.png" >   |   <img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/735195/fb6a3be1-1fd0-9222-5ddf-2de6635078fb.png" >   |   <img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/735195/231baa73-58a4-d93b-1f77-930973d0f746.gif" >   |

## 使用したパッケージ
今回は、このようなカルーセルが簡単にできる[carousel_slider](https://pub.dev/packages/carousel_slider)を採用しました。
パッケージ選定の条件的には問題なさそうです

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/735195/9bfa1870-5424-06a2-6e30-057627c58213.png" width="600">

## 実際のウィジェット（サンプル）

```dart: カルーセルに出したいリスト
final List<String> images = [
  'assets/kodai_sacabambaspis.png',
];
```

```dart:カルーセルの要素
class _CarouselCard extends StatelessWidget {
  const _CarouselCard({
    required this.image,
  });

  final String image;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 180,
      width: 120,
      decoration: BoxDecoration(
        color: Colors.black54,
        borderRadius: BorderRadius.circular(20),
      ),
      child: SizedBox(
        child: Image(
          image: AssetImage(image),
          fit: BoxFit.fitWidth,
        ),
      ),
    );
  }
}
```

### 今回のメインどころ

今回は、`CarouserlSlider.builder`を用いてカルーセルを設定しています。
`CarouselSlider`の`itemBuilder`は3つの引数を持ちます。

```dart
itemBuilder:
  (BuildContext context, int itemIndex, int pageViewIndex) {
　　　　return _CarouselCard(image: images[itemIndex]);
},
```
- 第一引数： お馴染みのBuildContext
- 第二引数： スライダーの要素のindexが取れます(`images`）
たとえば、配列要素が10件あった場合は0~9の値が取れます。
- 第三引数： 擬似的なindex（詳細は後ほど説明します）


```dart:スライドできるカルーセル
class _CarouselSlider extends StatelessWidget {
  const _CarouselSlider();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 24),
      child: SizedBox(
        height: 180,
        child: CarouselSlider.builder(
          itemCount: images.length,
          itemBuilder:
              (BuildContext context, int itemIndex, int pageViewIndex) {
            return _CarouselCard(image: images[itemIndex]);
          },
          options: CarouselOptions(
            height: 180,
            viewportFraction: 0.35, // カルーセルの横幅を管理します
          ),
        ),
      ),
    );
  }
}
```

```dart:気休めにボトムシートエリアです
class _BottomSheetWidget extends StatelessWidget {
  const _BottomSheetWidget();

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 500,
      width: double.maxFinite,
      // 上だけ丸める
      decoration: const BoxDecoration(
        borderRadius: BorderRadius.vertical(
          top: Radius.circular(20),
        ),
        color: Color.fromARGB(42, 255, 255, 255),
      ),
      child: const Padding(
        padding: EdgeInsets.symmetric(vertical: 20),
        child: Column(
          children: [
            _CarouselSlider(),
          ],
        ),
      ),
    );
  }
}

```

## 注意点
実は、このcarousel_sliderは無限ではありません。
`itemBuilder`の先ほど３つの引数の話をしましたが、その中の`pageViewIndex`はどうなっているのか調べてみました。
パッケージの該当箇所を見てみます。


```dart:carousel_slider-4.2.1/lib/carousel_state.dart
class CarouselState {
  /// The [CarouselOptions] to create this state
  CarouselOptions options;

  /// [pageController] is created using the properties passed to the constructor
  /// and can be used to control the [PageView] it is passed to.
  PageController? pageController;

  /// The actual index of the [PageView].
  ///
  /// This value can be ignored unless you know the carousel will be scrolled
  /// backwards more then 10000 pages.
  /// Defaults to 10000 to simulate infinite backwards scrolling.
  int realPage = 10000;

  /// The initial index of the [PageView] on [CarouselSlider] init.
  ///
  int initialPage = 0;

  /// The widgets count that should be shown at carousel
  int? itemCount;

  /// Will be called when using pageController to go to next page or
  /// previous page. It will clear the autoPlay timer.
  /// Internal use only
  Function onResetTimer;

  /// Will be called when using pageController to go to next page or
  /// previous page. It will restart the autoPlay timer.
  /// Internal use only
  Function onResumeTimer;

  /// The callback to set the Reason Carousel changed
  Function(CarouselPageChangedReason) changeMode;

  CarouselState(
      this.options, this.onResetTimer, this.onResumeTimer, this.changeMode);
}
```

以下にある通り、上限は10000要素だということがわかりました。

```dart:carousel_slider-4.2.1/lib/carousel_state.dart
  /// The actual index of the [PageView].
  ///
  /// This value can be ignored unless you know the carousel will be scrolled
  /// backwards more then 10000 pages.
  /// Defaults to 10000 to simulate infinite backwards scrolling.
  int realPage = 10000;
```

実際に10000個分もスクロールするユーザーは稀かと思いますが、実は無限ではないという点には気をつけておきましょう。

![kodai_sacabambaspis.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/735195/30dc600b-478c-24b5-40aa-e680afe4ea94.png)
サカバンバスピス