はじめに
flutterでドットインジケータ作ったので備忘録的に残します。
もっといい書き方や内容的に間違っているところはコメント・編集リクエスト等で 優しく 教えてください.
実装
完成コード
まずは完成コードを載せておきます。
完成コード
class PageViewWithDotIndicator extends HookWidget {
const PageViewWithDotIndicator({super.key});
@override
Widget build(BuildContext context) {
final pages = [
const SimplePage(
title: '0ページ',
contentText: '0ページ目のコンテンツです',
),
const SimplePage(
title: '1ページ',
contentText: '1ページ目のコンテンツです',
),
const SimplePage(
title: '2ページ',
contentText: '2ページ目のコンテンツです',
),
const SimplePage(
title: '3ページ',
contentText: '3ページ目のコンテンツです',
),
const SimplePage(
title: '4ページ',
contentText: '4ページ目のコンテンツです',
),
];
final pageController = usePageController();
final currentIndex = useState(
(pageController.positions.isNotEmpty ? pageController.page : 0)!.round(),
);
useEffect(
() {
void listener() {
if (pageController.positions.isNotEmpty) {
currentIndex.value = pageController.page!.round();
}
}
pageController.addListener(listener);
return () {
pageController.removeListener(listener);
};
},
[],
);
return Scaffold(
appBar: AppBar(
title: Text(
'現在のpageIndex : ${currentIndex.value.toString()}',
),
),
body: Stack(
children: [
PageView(
controller: pageController,
children: pages,
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(
bottom: 32,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (int i = 0; i < pages.length; i++)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
),
child: Container(
width: 16,
height: 16,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black.withOpacity(
currentIndex.value == i ? 1 : 0.1,
),
),
),
),
],
),
),
)
],
),
);
}
}
詳細な実装
flutter_hooksを導入しましょう
flutter pub add flutter_hooks
HooksWidgetの雛形を作ります。
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class PageViewWithDotIndicator extends HookWidget {
const PageViewWithDotIndicator({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
children: [],
),
);
}
}
適当にpageを作っておいてください
final pages = [
const SimplePage(
title: '0ページ',
contentText: '0ページ目のコンテンツです',
),
const SimplePage(
title: '1ページ',
contentText: '1ページ目のコンテンツです',
),
const SimplePage(
title: '2ページ',
contentText: '2ページ目のコンテンツです',
),
const SimplePage(
title: '3ページ',
contentText: '3ページ目のコンテンツです',
),
const SimplePage(
title: '4ページ',
contentText: '4ページ目のコンテンツです',
),
];
一応SimplePageのコードも載せておきます!
SimplePage
import 'package:flutter/material.dart';
class SimplePage extends StatelessWidget {
const SimplePage({
required this.title,
required this.contentText,
super.key,
});
final String title;
final String contentText;
@override
Widget build(BuildContext context) {
return Container(
color: Colors.amber[200],
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
contentText,
style: TextStyle(
fontSize: 30,
),
),
],
),
),
);
}
}
次にPageControllerを作成します.
title: '4ページ',
contentText: '4ページ目のコンテンツです',
),
];
// これを追記
final pageController = usePageController();
return Scaffold(
body: PageView(
現在のページ番号とページの変化を管理するuseState, useEffectを書きます.
final currentIndex = useState(
(pageController.positions.isNotEmpty ? pageController.page : 0),
);
// ページの移動を管理
useEffect(
() {
void listener() {
if (pageController.positions.isNotEmpty) {
currentIndex.value = pageController.page;
}
}
pageController.addListener(listener);
return () {
pageController.removeListener(listener);
};
},
[],
);
また、ウィジェットの部分をいい感じにします。PageViewに対してStackでドットの列を重ねて実装しています。
page番号は0から始まるので、現在のページ番号とドットのインデックスiが一致する時に透明度が1になるように作成しています。
return Scaffold(
appBar: AppBar(
title: Text(
'現在のpageIndex : ${currentIndex.value.toString()}',
),
),
body: Stack(
children: [
PageView(
controller: pageController,
children: pages,
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(
bottom: 32,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (int i = 0; i < pages.length; i++)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
),
child: Container(
width: 16,
height: 16,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black.withOpacity(
currentIndex.value == i ? 1 : 0.1,
),
),
),
),
],
),
),
)
],
),
);
一旦動かしてみましょう!
この状態で動かすと以下のgifのようになるかと思います。
ページが切り替わっている途中にどのインジケータも点灯していないことがわかるかと思います。
pageの値はページの遷移中は小数値になるので、うまく表示されないようですね.
これを解決するために、currentPage.valueの値を四捨五入して必ず整数値になるように調整します。
以下のように書き換えてみてください.
final currentIndex = useState(
(pageController.positions.isNotEmpty ? pageController.page : 0)!.floor(),
);
useEffect(
() {
void listener() {
if (pageController.positions.isNotEmpty) {
currentIndex.value = pageController.page!.floor();
}
}
pageController.addListener(listener);
return () {
pageController.removeListener(listener);
};
},
[],
);
これでうまく動作するはずです!
終わりに
今回はドットインジケータを実装してみました。
もし皆さんの参考になれば幸いです🙏