こんにちは、CX事業本部 Delivery部の若槻です。
Flutterでモバイルアプリを開発しているのですが、アプリインスントール後の初回起動時にユーザーに対してアプリの使い方や目的を説明するためのウォークスルー機能を実装することになり、そのためのUIとしてページインジケーターを採用することになりました。
ページインジケータを実装できるパッケージを探してみたところsmooth_page_indicatorが良さそうでしたので試してみました。
Exampleを動かしてみる
まずは下記のExampleコードを動かしてみます。(基本的にExampleそのままですが、一部Warningを抑制する修正をしています。)
lib/main.dart
import 'package:flutter/material.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Smooth Page Indicator Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final controller = PageController(viewportFraction: 0.8, keepPage: true);
@override
Widget build(BuildContext context) {
final pages = List.generate(
6,
(index) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: Colors.grey.shade300,
),
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
child: SizedBox(
height: 280,
child: Center(
child: Text(
"Page $index",
style: const TextStyle(color: Colors.indigo),
)),
),
));
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const SizedBox(height: 16),
SizedBox(
height: 240,
child: PageView.builder(
controller: controller,
// itemCount: pages.length, // ループの制御
itemBuilder: (_, index) {
return pages[index % pages.length];
},
),
),
const Padding(
padding: EdgeInsets.only(top: 24, bottom: 12),
child: Text(
'Worm',
style: TextStyle(color: Colors.black54),
),
),
SmoothPageIndicator(
controller: controller,
count: pages.length,
effect: const WormEffect(
dotHeight: 16,
dotWidth: 16,
type: WormType.thin,
strokeWidth: 5,
),
),
const Padding(
padding: EdgeInsets.only(top: 16, bottom: 8),
child: Text(
'Jumping Dot',
style: TextStyle(color: Colors.black54),
),
),
SmoothPageIndicator(
controller: controller,
count: pages.length,
effect: const JumpingDotEffect(
dotHeight: 16,
dotWidth: 16,
jumpScale: .7,
verticalOffset: 15,
),
),
const Padding(
padding: EdgeInsets.only(top: 16, bottom: 12),
child: Text(
'Scrolling Dots',
style: TextStyle(color: Colors.black54),
),
),
SmoothPageIndicator(
controller: controller,
count: pages.length,
effect: const ScrollingDotsEffect(
activeStrokeWidth: 2.6,
activeDotScale: 1.3,
maxVisibleDots: 5,
radius: 8,
spacing: 10,
dotHeight: 12,
dotWidth: 12,
)),
const Padding(
padding: EdgeInsets.only(top: 16, bottom: 16),
child: Text(
'Customizable Effect',
style: TextStyle(color: Colors.black54),
),
),
SmoothPageIndicator(
controller: controller,
count: pages.length,
effect: CustomizableEffect(
activeDotDecoration: DotDecoration(
width: 32,
height: 12,
color: Colors.indigo,
rotationAngle: 180,
verticalOffset: -10,
borderRadius: BorderRadius.circular(24),
// dotBorder: const DotBorder(
// padding: 2,
// width: 2,
// color: Colors.indigo,
// ),
),
dotDecoration: DotDecoration(
width: 24,
height: 12,
color: Colors.grey,
// dotBorder: DotBorder(
// padding: 2,
// width: 2,
// color: Colors.grey,
// ),
// borderRadius: BorderRadius.only(
// topLeft: Radius.circular(2),
// topRight: Radius.circular(16),
// bottomLeft: Radius.circular(16),
// bottomRight: Radius.circular(2)),
borderRadius: BorderRadius.circular(16),
verticalOffset: 0,
),
spacing: 6.0,
// activeColorOverride: (i) => colors[i],
inActiveColorOverride: (i) => colors[i],
),
),
const SizedBox(height: 32.0),
],
),
),
),
);
}
}
const colors = [
Colors.red,
Colors.green,
Colors.greenAccent,
Colors.amberAccent,
Colors.blue,
Colors.amber,
];
Exampleを起動してみると、下記のようなページインジケータが実装できています。
解説
smooth_page_indicatorは、SmoothPageIndicator()
にcontroller
とcount
を渡すことで、ページインジケータを実装することができます。
PageView classで引き受けるPageController classのコントローラーを、SmoothPageIndicator()
でも引き受けることにより、スクロールによるページ送り(scroll offset)に合わせてアニメーションが描画されます。
lib/main.dart
class _HomePageState extends State<HomePage> {
final controller = PageController(viewportFraction: 0.8, keepPage: true);
@override
Widget build(BuildContext context) {
final pages = List.generate(
6,
(index) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: Colors.grey.shade300,
),
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
child: SizedBox(
height: 280,
child: Center(
child: Text(
"Page $index",
style: const TextStyle(color: Colors.indigo),
)),
),
));
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const SizedBox(height: 16),
SizedBox(
height: 240,
child: PageView.builder(
controller: controller,
// itemCount: pages.length, // ループの制御
itemBuilder: (_, index) {
return pages[index % pages.length];
},
),
),
const Padding(
padding: EdgeInsets.only(top: 24, bottom: 12),
child: Text(
'Worm',
style: TextStyle(color: Colors.black54),
),
),
SmoothPageIndicator(
controller: controller,
count: pages.length,
effect: const WormEffect(
dotHeight: 16,
dotWidth: 16,
type: WormType.thin,
strokeWidth: 5,
),
),
Effectでアニメーションのパターンを指定可能
SmoothPageIndicator()
のeffect
ではページ送り時のアニメーションのパターンを指定できます。Exampleでは、下記のパターンを利用しています。
パターン | 説明 |
---|---|
WormEffect | 蠕虫の動きのようなアニメーション |
JumpingDotEffect | ドットが飛び跳ねるアニメーション |
ScrollingDotsEffect | ドットが水平にスクロールするアニメーション |
CustomizableEffect | カスタマイズ可能なアニメーション |
ソースコードを見ると他にもEffectがあるようですので、表現したいUIに応じて選択してください。
PageViewのループにも対応可能
ExampleではPageView
でitemCount
を未指定だったため、ページ送りのループが有効となっていました。
lib/main.dart
child: PageView.builder(
controller: controller,
itemCount: pages.length, // ループの制御
itemBuilder: (_, index) {
return pages[index % pages.length];
},
),
itemCount
を指定することで、ループを無効化することができますが、その場合でもSmoothPageIndicator()
では対応可能です。
おわりに
Flutterでページインジケーター機能の実装を試してみました。
SmoothPageIndicator()
は、PageView
のスクロールに合わせてアニメーションを描画することができるため、ページインジケーターのUIを実装する際に便利なパッケージだと思います。
以上