環境
- MaxOS Monterey
- Flutter 2.8.1
- Dart 2.15.1
Flutter&Riverpod でスライダーを実装しました。
より良いコーディングやアーキテクチャなどあればコメント欄にお願いいたします!
完成形
フォルダ構成
flutter_app/
├ images/
│ └ image.jpg
├ lib/
│ ├ models/
│ │ └ image_slider.dart
│ ├ providers/
│ │ └ image_sliders.dart
│ ├ screens/
│ │ └ screen.dart
│ └ main.dart
└ pubspec.yaml
実装
package
最新バージョンなど詳細は公式でご確認ください
・carousel_slider
・hooks_riverpod
・flutter_hooks
pubspec.yaml
carousel_slider: ^4.0.0
hooks_riverpod: ^1.0.3
flutter_hooks: ^0.18.1
Main
-
Riverpod を使用するため main.dart に hooks_riverpod
をインポート - MyApp() を ProviderScope() で囲う
main.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; // 追加
import './screens/slider.dart';
void main() {
runApp(
const ProviderScope( // 追加
child: MyApp(),
)
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: SliderScreen(),
);
}
}
Model
image_slider.dart
import 'package:flutter/cupertino.dart';
class ImageSlider {
final Image image;
ImageSlider({ required this.image });
}
Provider
ドットをクリックした時に、画像を変更したいので
carousel_controller のインポートとコントローラを定義
image_sliders.dart
import 'dart:collection'; // UnmodifiableListView の為
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_controller.dart'; // 追加
import '../models/image_slider.dart';
class ImageSliders extends ChangeNotifier {
// お好きな画像を定義
final List<ImageSlider> _imageSliders = [
ImageSlider(image: Image.asset('images/dog1.jpg')),
ImageSlider(image: Image.asset('images/dog2.jpg')),
ImageSlider(image: Image.asset('images/dog3.jpg')),
ImageSlider(image: Image.asset('images/dog4.jpg')),
ImageSlider(image: Image.asset('images/dog5.jpg')),
];
int _currentPoint = 0;
// 追加
CarouselController buttonCarouselController = CarouselController();
// 他ファイルで _imageSliders に対して、add などの処理を防ぐ?
UnmodifiableListView<ImageSlider> get imageSliders {
return UnmodifiableListView(_imageSliders);
}
int get currentPoint {
return _currentPoint;
}
void setCurrentPoint(int index) {
_currentPoint = index;
notifyListeners(); // 変更を通知
}
}
View
Riverpod を実装
- hooks_riverpod をインポート
- プロバイダーを定義
- extends を HookConsumerWidget に変更
- Widget build() の引数に、WidgetRef ref を追加
スライダーを実装
- ref.read(imageSliderProvider).imageSliders で定義した画像を呼び出し
- CarouselSlider(~) でスライダー実装(詳しくは公式参照)
ref.watch, ref.read があるが、
変更を検知して表示を変更したいなら、watch (Dotsで使用)
呼び出すだけで良いなら、read
で使い分けるっぽい
slider.dart
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart'; // 追加
import 'package:hooks_riverpod/hooks_riverpod.dart'; // 追加
import '../models/image_slider.dart';
import '../providers/image_sliders.dart';
// プロバイダー定義
final imageSliderProvider = ChangeNotifierProvider((ref) => ImageSliders());
class SliderScreen extends HookConsumerWidget { // 変更
const SliderScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) { // 追加
// 画像の読み込み
final List<ImageSlider> _imageSliders = ref.read(imageSliderProvider).imageSliders;
return Scaffold(
appBar: AppBar(
title: const Text('Dog Slider'),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 30.0),
child: Column(
children: [
CarouselSlider( // スライダー
carouselController: ref.read(imageSliderProvider).buttonCarouselController, // DotsIndicator用
options: CarouselOptions(
enlargeCenterPage: true, // 真ん中の画像以外を少し小さく表示
height: MediaQuery.of(context).size.height / 4, // 高さ(お好みで)
viewportFraction: 0.75, // 画像の横幅
autoPlay: true, // 自動スクロール
autoPlayInterval: const Duration(seconds: 5), // 自動スクロールの間隔
onPageChanged: (index, _) {
// 現在の画像の位置を設定
ref.read(imageSliderProvider).setCurrentPoint(index);
},
),
items: _imageSliders.map((imageSlider) {
return Builder(
builder: (BuildContext context) {
return SizedBox(
width: MediaQuery.of(context).size.width,
child: imageSlider.image,
);
},
);
}).toList(),
),
DotsIndicator(imageSliders: _imageSliders),
],
),
),
);
}
}
~~
続く
~~
ドットインディケーター
- DotsをWidget で分けているが、どちらでも
- ref.watch(topSliderProvider).currentPoint で変更を検知し、カラーを変更
- .buttonCarouselController.animateToPage(index)でタップした箇所の画像へスライド
slider.dart
~~
続き
~~
// ドット部分
class DotsIndicator extends HookConsumerWidget {
const DotsIndicator({
Key? key,
required List<TopSlider> topSliders,
}) : _topSliders = topSliders, super(key: key);
final List<TopSlider> _topSliders;
@override
Widget build(BuildContext context, WidgetRef ref) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _topSliders.map((topSlider) {
int index = _topSliders.indexOf(topSlider);
return GestureDetector(
child: Container(
width: 8.0,
height: 8.0,
margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 2.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: ref.watch(topSliderProvider).currentPoint == index ?
const Color.fromRGBO(0, 0, 0, 0.9) : const Color.fromRGBO(0, 0, 0, 0.4),
),
),
onTap: () => ref.read(topSliderProvider).buttonCarouselController.animateToPage(index),
);
}).toList(),
);
}
}
これで動くはず!