Closed15

Flutter の carousel_slider を使ってみる

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Getting Started

コード

lib/main.dart
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: CarouselSlider(
          options: CarouselOptions(
            height: 400.0,
          ),
          items: [1, 2, 3].map((i) {
            return Container(
              width: MediaQuery.of(context).size.width,
              margin: const EdgeInsets.symmetric(horizontal: 5.0),
              decoration: const BoxDecoration(color: Colors.amber),
              child: Text('text $i', style: const TextStyle(fontSize: 16.0)),
            );
          }).toList(),
        ),
      ),
    );
  }
}

実行結果

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

サイズの調整

CarouselOptionsviewportFraction を設定することで変更できる

CarouselOptions(
  height: 400.0,
  viewportFraction: 0.9
)

CarouselOptions(
  height: 400.0,
  viewportFraction: 0.6
)

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ところで MediaQuery とは何なのか?

わかりやすい Zenn 記事

https://zenn.dev/chooyan/articles/3c2691e93f4038

わかりやすい動画

https://www.youtube.com/watch?v=A3WrA4zAaPw

公式ドキュメント

https://api.flutter.dev/flutter/widgets/MediaQuery-class.html

推測するに下記のコードは画面のサイズを取得しているのだろう

MediaQuery.of(context).size.width

上記の代わりに double.infinity でも良い気がするけどどうなんだろう

CarouselSlider(
  options: CarouselOptions(
    height: 400.0,
    viewportFraction: 0.6
  ),
  items: [1, 2, 3].map((i) {
    return Container(
      width: double.infinity,
      margin: const EdgeInsets.symmetric(horizontal: 5.0),
      decoration: const BoxDecoration(color: Colors.amber),
      child: Text('text $i', style: const TextStyle(fontSize: 16.0)),
    );
  }).toList(),
),

大丈夫みたいだけどなんとなく危ない感じがするのでやめておこう

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

次は無限スクロールを試してみる

まずは無限スクロールの認識が違っていた

carousel_slider では無限スクロールとは下図のようにループしないカルーセルのようだ

ループさせない場合は下記のように enableInfiniteScroll を明示的に false に設定する(デフォルトでは true

CarouselOptions(
  enableInfiniteScroll: false,
)
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

でもほぼ無限にスクロールさせることはできる

CarouselSlider.builder を使うとカルーセルページを動的に生成することもできる

CarouselSlider.builder(
  options: CarouselOptions(
    height: 400.0,
    enableInfiniteScroll: false,
  ),
  itemCount: 2,
  itemBuilder: (context, index, realIndex) {
    return Container(
      width: MediaQuery.of(context).size.width,
      margin: const EdgeInsets.symmetric(horizontal: 5.0),
      decoration: const BoxDecoration(color: Colors.amber),
      child: Text(
        'index $index, realIndex $realIndex',
        style: const TextStyle(fontSize: 16.0),
      ),
    );
  },
)

結果は下図の通り

itemCount は必須の引数なので指定する必要があり、これでは無限にはできない感じがする

しかし itemBuilder には realIndex という引数があり、これを上手に使えばほぼ無限にスクロールさせることができそう

realIndex は 10000 から始まって0が最小値であることを確認した

carousel_state.dart のソースコードの realPage を変更すると楽に確認できる(1万回左にスクロールしても良いが...)

1万回も左にスクロールする人は稀だと思うので多くの場合はあまり気にしなくて良さそう

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

イベント

onPageChanged オプションを使うことでページ変更時のイベントハンドラを設定できる

CarouselOptions(
  height: 400.0,
  onPageChanged: (index, reason) {
    print('index $index, reason $reason');
  },
),
flutter: index 0, reason CarouselPageChangedReason.manual
flutter: index 1, reason CarouselPageChangedReason.manual

残念ながらこちらは index しか返してくれないので realIndex は取得できない

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

showDialog と組み合わせる

Scaffold(
  appBar: AppBar(
    title: const Text('Flutter Demo Home Page'),
  ),
  body: Container(),
  floatingActionButton: FloatingActionButton.extended(
    onPressed: () {
      showDialog(
        context: context,
        builder: (context) {
          return CarouselSlider.builder(
            options: CarouselOptions(
              height: 400.0,
              onPageChanged: (index, reason) {
                print('index $index, reason $reason');
              },
            ),
            itemCount: 2,
            itemBuilder: (context, index, realIndex) {
              return Container(
                width: MediaQuery.of(context).size.width,
                margin: const EdgeInsets.symmetric(horizontal: 5.0),
                decoration: const BoxDecoration(color: Colors.amber),
                child: Text(
                  'index $index, realIndex $realIndex',
                  style: const TextStyle(fontSize: 16.0),
                ),
              );
            },
          );
        },
      );
    },
    label: const Text('Show carousel'),
  ),
)

表示自体はされたがテキストに赤い線が表示されていて何か足りない所がある様子だ

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

普通にコンテナをダイアログ表示させてもエラーは出る

showDialog(
  context: context,
  builder: (context) {
    return Container(
      width: MediaQuery.of(context).size.width,
      margin: const EdgeInsets.symmetric(horizontal: 5.0),
      decoration: const BoxDecoration(color: Colors.amber),
      child: const Text(
        "I'm a container.",
        style: TextStyle(fontSize: 16.0),
      ),
    );
  },
);

したがって CarouselSlider が悪い訳では無さそう

ダイアログを適切に表示させる方法について調べてみよう

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ダイアログとして表示できた

Dialog クラスを使ったらモーダル風にカルーセルを表示できた

showDialog(
  context: context,
  builder: (context) {
    return Dialog(
      backgroundColor: Colors.transparent,
      insetPadding: EdgeInsets.zero,
      child: CarouselSlider.builder(
        options: CarouselOptions(
          height: 400.0,
        ),
        itemCount: 10,
        itemBuilder: (context, index, realIndex) {
          return Container(
            width: MediaQuery.of(context).size.width,
            margin: const EdgeInsets.symmetric(horizontal: 5.0),
            decoration: const BoxDecoration(color: Colors.amber),
            child: Text(
              'index $index, realIndex $realIndex',
              style: const TextStyle(fontSize: 16.0),
            ),
          );
        },
      ),
    );
  },
);

backgroundColorinsetPadding を調整が必要

スライドとスライドの間を押してもモーダルが消えないのは悩ましいが仕方がない

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

最初のページを指定する

基本的なことですが initialPage オプションを使用すると最初に表示されるページを指定できる

CarouselOptions(
  height: 400.0,
  initialPage: 5,
)
このスクラップは2023/01/10にクローズされました