【Flutter】画像付きのスライドショーを実装する方法【carousel_slider】

Flutter

はじめに

ECアプリの商品詳細画面でよく見かけるスライドショーを実装したいと思ったことはありませんか?

今回はスライドショーが実装可能になる「carousel_slider」というライブラリをご紹介します。

carousel_sliderとは、Flutterのライブラリのことで画像やウィジェットをスライドショーとして動かすことができます。

使用環境

MacBookAir: Apple M1

macOS: 14.5

Flutter バージョン: 3.16.0

carousel_slider: ^5.0.0

実装

今回はインジケータ付きのカルーセルを実装してみました。

無印良品のアプリも丸型のインジケータですね。

以下、動作画面と実装コードです。

実装コード

【carousel_slider.dart】

final carouselCurrentIndexProvider = StateProvider<int>((ref) => 0);

class CarouselSliderScreen extends ConsumerWidget {
  const CarouselSliderScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    int carouselCurrentIndex = ref.watch(carouselCurrentIndexProvider);

    final List<String> imagePathList = [
      'assets/images/carousel_na.png',
      'assets/images/carousel_na_2.png',
      'assets/images/carousel_ko.png',
      'assets/images/carousel_shi.png',
    ];

    return Scaffold(
      appBar: AppBar(
        title: const Text('CarouselSliderScreen'),
        automaticallyImplyLeading: true,
      ),
      body: Center(
        child: Column(
          children: [
            CarouselSlider.builder(
              options: CarouselOptions(
                  height: 300,
                  initialPage: carouselCurrentIndex,
                  enlargeCenterPage: true,
                  onPageChanged: (index, reason) {
                    ref.watch(carouselCurrentIndexProvider.notifier).state =
                        carouselCurrentIndex = index;
                  }),
              itemCount: imagePathList.length,
              itemBuilder: (context, itemIndex, pageViewIndex) {
                final path = imagePathList[itemIndex];
                return viewImage(path);
              },
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: imagePathList.map((path) {
                final int getIndex = imagePathList.indexOf(path);
                return Container(
                  width: 15,
                  height: 15,
                  margin: const EdgeInsets.symmetric(
                      vertical: 10.0, horizontal: 5.0),
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: carouselCurrentIndex == getIndex
                        ? const Color.fromRGBO(115, 137, 187, 1)
                        : const Color.fromRGBO(115, 137, 187, 0.4),
                  ),
                );
              }).toList(),
            ),
          ],
        ),
      ),
    );
  }
}

Widget viewImage(path) {
  return Image.asset(
    path,
    fit: BoxFit.cover,
  );
}

解説

スライダー番号を監視する

// スライダー番号を監視
final carouselCurrentIndexProvider = StateProvider<int>((ref) => 0);
// スライダー番号
int carouselCurrentIndex = ref.watch(carouselCurrentIndexProvider);

インジケータの色を動的に変更させる為に現在のスライダー番号を監視します。

今回はRiverpodのStateProviderで監視します。

配列のインデックスは0から始まるので初期値は0にします。

監視しているスライダー番号をcarouselCurrentIndexと定義します。

スライダーに表示したい画像を設定

// スライダーに表示する画像のパスのリスト
final List<String> imagePathList = [
  'assets/images/carousel_na.png',
  'assets/images/carousel_na_2.png',
  'assets/images/carousel_ko.png',
  'assets/images/carousel_shi.png',
];

スライダーに表示したい画像のパスをList <String>型で定義します。

今回はローカルから画像を呼び出したいのでプロジェクトディレクトリからパスを指定しています。

詳しくは後述の「ソースコード」を参照

// 表示したい画像のウィジェット
Widget viewImage(path) {
  return Image.asset(
    path,
    fit: BoxFit.cover,
  );
}

画像を表示する箱(Widget)を作成しておきます。

引数pathには先ほどのimagePathListのパスが入ります。

Image.asset()クラスで画像の呼び出しを行っています。

CarouselSliderの作成

CarouselSlider.builder(
    options: CarouselOptions(
        height: 300,
        initialPage: carouselCurrentIndex,
        enlargeCenterPage: true,
        // カルーセルのページが変化した時の処理
        // 変化後のページ番号をcarouselCurrentIndexに反映
        onPageChanged: (index, reason) {
          ref.watch(carouselCurrentIndexProvider.notifier).state =
              carouselCurrentIndex = index;
        }),
    // アイテム数=画像の数
    itemCount: imagePathList.length,
    // スライダーを変更すると呼び出される
    itemBuilder: (context, itemIndex, pageViewIndex) {
      final path = imagePathList[itemIndex];
      // カルーセルに表示するアイテム
      return viewImage(path);
....
  • CarouselSlider.builder
    カルーセルスライダーを生成
  • CarouselOptions
    カルーセルスライダーのオプション項目
  • onPageChanged
    カルーセルのページが変化した時の処理
    →切り替わり後のページ番号をcarouselCurrentIndexに反映させている
  • itemCount
    表示したいアイテム数を指定
  • itemBuilder
    スライダーに表示したいWidgetを生成

インジケータの作成

// インジケータを横に並べる
Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: imagePathList.map((path) {
    // リスト番号を取得
    final int getIndex = imagePathList.indexOf(path);
    return Container(
      width: 15,
      height: 15,
      margin: const EdgeInsets.symmetric(
          vertical: 10.0, horizontal: 5.0),
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: carouselCurrentIndex == getIndex
               ? const Color.fromRGBO(115, 137, 187, 1)
               : const Color.fromRGBO(115, 137, 187, 0.4),
      ),
    );
  }).toList(),
),

ここでスライダー下のインジケータを作成

children: imagePathList.map((path){ <処理の内容> }).toList()

mapを使用し、imagePathListを一つづつ処理してインジケータのListを生成します。

インジケータの色を動的に変更

 decoration: BoxDecoration(
            shape: BoxShape.circle,
            // リスト番号とcarouselCurrentIndexを比較してインジケータの色を決定
            color: carouselCurrentIndex == getIndex
               ? const Color.fromRGBO(115, 137, 187, 1)
               : const Color.fromRGBO(115, 137, 187, 0.4),

三項演算子を使用し、スライダーの動きと連動してインジケータの色の切り替えを行う

条件式 ( carouselCurrentIndex が getIndex(0~3の配列番号)と等しい )
? ( trueなら濃い青 ) : ( falseなら薄い青 ) になる

つまり、今表示されているスライドのインジケータが濃い青色になり、それ以外は薄い青色になる

ソースコード

全体のソースコードになります。

learning_app/practice_app/lib/view/carousel_slider.dart at main · nanakoshis/learning_app
学習・練習用リポジトリ. Contribute to nanakoshis/learning_app development by creating an account on GitHub.

まとめ

  • CarouselSlider.builderでカルーセルスライダーが生成できる
  • onPageChangedでカルーセルのページの変化を検知できる
  • 状態管理ライブラリと組み合わせることで動的なインジケータが作成できる

いかがだったでしょうか?

ご意見、ご感想ありましたらお気軽にコメントしてください。

carousel_sliderを使うとお手軽にスライダー機能が実装できます。

実装次第ではリッチなUIを実現することができるでしょう。

【参考文献】

コメント

タイトルとURLをコピーしました