FlutterにおけるonPageChanged
は、PageViewウィジェットのページが変更された際に発火するコールバック関数です。PageViewは、ユーザーが左右にスワイプすることで複数のページを切り替えられるウィジェットで、アプリのチュートリアル画面や画像ギャラリー、タブ形式のコンテンツ表示など、様々な場面で利用されます。
onPageChanged
を使用することで、現在表示されているページ番号を取得したり、ページ遷移に合わせてアニメーションを制御したり、ページインジケーターを更新したりといった処理を行うことができます。つまり、onPageChanged
は、PageViewを使ったページング機能をよりインタラクティブで使いやすくするための重要な要素なのです。
このコールバック関数を適切に活用することで、ユーザーエクスペリエンス(UX)を大幅に向上させることが可能です。例えば、ページが変わる際にスムーズなアニメーションを追加したり、現在のページ位置を視覚的に示すインジケーターを表示したりすることで、ユーザーは自分がどのページにいるのか、あとどれくらいのコンテンツがあるのかを直感的に理解できるようになります。
本記事では、onPageChanged
の基本的な使い方から、具体的な実装例、パフォーマンスの最適化まで、詳しく解説していきます。onPageChanged
をマスターして、より洗練されたFlutterアプリを開発しましょう。
onPageChanged
を理解するためには、まずPageView
とPageController
について理解しておく必要があります。
PageView
は、複数のウィジェット(ページ)を横または縦方向にスクロール可能なリストとして表示するためのウィジェットです。ユーザーはスワイプ操作によってページを切り替えることができます。
PageView
にはいくつかの種類があります。
- PageView: 標準的なPageViewです。
- PageView.builder: 大量のページを効率的に生成する場合に使用します。必要なページのみをレンダリングするため、メモリ使用量を抑えることができます。
- PageView.custom: 独自のページ遷移ロジックを実装する場合に使用します。
PageView
は、children
プロパティにウィジェットのリストを受け取ります。これらのウィジェットが個別のページとして表示されます。
PageController
は、PageView
のスクロール動作を制御するためのコントローラです。PageView
の初期ページを設定したり、指定したページにアニメーション付きで移動したり、現在のページ番号を取得したりすることができます。
PageController
はPageView
のcontroller
プロパティに渡されます。
基本的な使い方:
final PageController _pageController = PageController(
initialPage: 0, // 初期ページ(デフォルトは0)
);
PageView(
controller: _pageController,
children: [
Container(color: Colors.red),
Container(color: Colors.green),
Container(color: Colors.blue),
],
)
PageControllerの主な機能:
- initialPage: 初期表示するページを指定します。
- viewportFraction: ページの一部を表示し、隣接するページを部分的に見せることで、スクロール可能なことを視覚的に表現できます。
- animateToPage: 指定したページにアニメーション付きで移動します。
- jumpToPage: 指定したページに瞬時に移動します。
- page: 現在表示されているページ番号を取得します(double型)。
PageController
とPageView
を組み合わせることで、高度なページング機能を実装することができます。onPageChanged
は、PageController
によって制御されるPageView
の状態変化を検知するために使用されます。
onPageChanged
は、PageView
ウィジェットのプロパティの一つであり、ページが切り替わったときに呼び出される関数です。この関数は、現在のページのインデックス(0から始まる整数)を引数として受け取ります。
基本的な実装例:
import 'package:flutter/material.dart';
class MyPageView extends StatefulWidget {
@override
_MyPageViewState createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
final PageController _pageController = PageController();
int _currentPage = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('onPageChanged Example'),
),
body: PageView(
controller: _pageController,
children: [
Container(
color: Colors.red,
child: Center(child: Text('Page 1')),
),
Container(
color: Colors.green,
child: Center(child: Text('Page 2')),
),
Container(
color: Colors.blue,
child: Center(child: Text('Page 3')),
),
],
onPageChanged: (int page) {
setState(() {
_currentPage = page;
});
print('Current Page: $page');
},
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentPage,
onTap: (int index) {
_pageController.animateToPage(
index,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Page 1'),
BottomNavigationBarItem(icon: Icon(Icons.business), label: 'Page 2'),
BottomNavigationBarItem(icon: Icon(Icons.school), label: 'Page 3'),
],
),
);
}
}
解説:
-
_pageController
の作成:PageController
を初期化し、PageView
のcontroller
プロパティに渡します。 -
onPageChanged
の定義:PageView
のonPageChanged
プロパティに、ページが切り替わった際に実行される関数を定義します。 -
setState
による状態更新:onPageChanged
内でsetState
を呼び出し、現在のページ番号を保持する_currentPage
変数を更新します。これにより、UIが再描画され、ページ番号の変化が反映されます。 -
ページ番号の表示:
print('Current Page: $page');
でコンソールに現在のページ番号を出力しています。 実際には、このpage
の値を使って、インジケーターを更新したり、別のアクションを実行したりします。 -
ボトムナビゲーションの実装例:
BottomNavigationBar
を使って、各ページに直接移動できるボタンを配置しています。onTap
で_pageController.animateToPage
を呼び出し、指定したページにアニメーション付きで移動しています。
ポイント:
-
onPageChanged
は、ページが完全に切り替わった後にのみ呼び出されます。スワイプ中に何度も呼び出されるわけではありません。 -
setState
を使用することで、UIを再描画し、ページ番号の変化を反映させます。 -
onPageChanged
の引数page
は、現在のページのインデックス(0から始まる整数)です。
この基本的な実装を理解することで、onPageChanged
を様々な場面で活用できるようになります。次のセクションでは、onPageChanged
の引数について詳しく見ていきましょう。
onPageChanged
の引数として渡されるpage
は、int
型の整数で、現在表示されているページのインデックスを表します。インデックスは0から始まるため、最初のページは0、次のページは1、その次は2、…となります。
このpage
の値を取得することで、以下のような処理を行うことができます。
- 現在のページ番号を表示する: 画面上に「現在xページ / 全yページ」のような形でページ番号を表示する。
-
特定ページのコンテンツを読み込む:
page
の値に基づいて、APIから該当ページのコンテンツを読み込む。 - ページインジケーターを更新する: ドットや線で現在位置を示すページインジケーターを更新する。
- 特定のページに到達したときにイベントを発火する: 例えば、最後のページに到達したときにチュートリアル完了のダイアログを表示する。
例:現在のページ番号を表示する
import 'package:flutter/material.dart';
class MyPageView extends StatefulWidget {
@override
_MyPageViewState createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
final PageController _pageController = PageController();
int _currentPage = 0;
final int _totalPages = 3; // 総ページ数
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('onPageChanged Example'),
),
body: Column(
children: [
Expanded(
child: PageView(
controller: _pageController,
children: [
Container(
color: Colors.red,
child: Center(child: Text('Page 1')),
),
Container(
color: Colors.green,
child: Center(child: Text('Page 2')),
),
Container(
color: Colors.blue,
child: Center(child: Text('Page 3')),
),
],
onPageChanged: (int page) {
setState(() {
_currentPage = page;
});
},
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text('現在 ${ _currentPage + 1 } / $_totalPages ページ'),
),
],
),
);
}
}
解説:
-
_totalPages
変数で総ページ数を定義しています。 -
onPageChanged
内で_currentPage
を更新しています。 -
Text('現在 ${ _currentPage + 1 } / $_totalPages ページ')
で、現在のページ番号を画面に表示しています。_currentPage
は0から始まるため、表示する際は+1しています。
注意点:
-
page
の値は、ページ遷移が完了した後に更新されます。スワイプ中にリアルタイムに更新されるわけではありません。 -
page
の値は、PageView.builder
を使用している場合でも同様に、表示されているページのインデックスを表します。
page
の値を効果的に活用することで、ユーザーに分かりやすく、インタラクティブなページング体験を提供することができます。次のセクションでは、onPageChanged
とアニメーションを連携させる方法について見ていきましょう。
onPageChanged
を利用することで、ページ遷移に合わせて様々なアニメーションを実装し、ユーザーエクスペリエンスを向上させることができます。
1. フェードイン・フェードアウトアニメーション:
ページが切り替わる際に、現在のページをフェードアウトさせ、新しいページをフェードインさせるアニメーションです。
import 'package:flutter/material.dart';
class MyPageView extends StatefulWidget {
@override
_MyPageViewState createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
final PageController _pageController = PageController();
int _currentPage = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Fade Animation Example'),
),
body: PageView(
controller: _pageController,
children: [
FadeAnimatedContainer(color: Colors.red, pageIndex: 0, currentPage: _currentPage),
FadeAnimatedContainer(color: Colors.green, pageIndex: 1, currentPage: _currentPage),
FadeAnimatedContainer(color: Colors.blue, pageIndex: 2, currentPage: _currentPage),
],
onPageChanged: (int page) {
setState(() {
_currentPage = page;
});
},
),
);
}
}
class FadeAnimatedContainer extends StatelessWidget {
final Color color;
final int pageIndex;
final int currentPage;
const FadeAnimatedContainer({
Key? key,
required this.color,
required this.pageIndex,
required this.currentPage,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return AnimatedOpacity(
opacity: pageIndex == currentPage ? 1.0 : 0.0,
duration: Duration(milliseconds: 300),
child: Container(
color: color,
child: Center(child: Text('Page ${pageIndex + 1}')),
),
);
}
}
解説:
-
FadeAnimatedContainer
というカスタムウィジェットを作成し、AnimatedOpacity
を使ってフェードイン・フェードアウトのアニメーションを実現しています。 -
pageIndex
とcurrentPage
を比較し、opacity
を切り替えることで、現在のページのみを表示するようにしています。
2. スケールアニメーション:
ページが切り替わる際に、現在のページを縮小させ、新しいページを拡大させるアニメーションです。
同様にTransform.scale
とAnimatedScale
を組み合わせて実装することができます。onPageChanged
で_currentPage
を更新し、pageIndex
とcurrentPage
を比較して、スケール値を調整します。
3. トランスレーションアニメーション:
ページが切り替わる際に、ページを左右にスライドさせるアニメーションです。
Transform.translate
とAnimatedPositioned
を組み合わせて実装することができます。
4. カスタムアニメーション:
onPageChanged
でアニメーションコントローラを操作し、より複雑なアニメーションを実装することも可能です。
ポイント:
-
AnimatedOpacity
、Transform.scale
、Transform.translate
、AnimatedPositioned
などのアニメーションウィジェットを活用することで、簡単にアニメーションを実装できます。 -
onPageChanged
でsetState
を呼び出し、アニメーションの状態を更新します。 -
AnimationController
を使うことで、より高度なアニメーションを制御できます。 - アニメーションのdurationを調整することで、アニメーションの速度を調整できます。
- 適切なアニメーションを実装することで、ユーザーにスムーズで快適なページング体験を提供できます。
アニメーションを効果的に活用することで、PageView
の使いやすさを向上させることができます。次のセクションでは、onPageChanged
を使ってページインジケーターを実装する方法について見ていきましょう。
ページインジケーターは、現在表示されているページが全体の何ページ中何ページ目なのかを視覚的に示すUI要素です。これにより、ユーザーはコンテンツの全体像を把握しやすくなり、より快適な操作が可能になります。onPageChanged
を活用することで、このインジケーターを動的に更新することができます。
一般的なインジケーターの種類:
- ドットインジケーター: 現在のページに対応するドットを強調表示するシンプルなインジケーター。
- ラインインジケーター: 現在のページに対応するラインを強調表示するインジケーター。
- 数値インジケーター: 現在のページ番号と総ページ数を数字で表示するインジケーター (例: 1/5, 2/5, …)。
- プログレスバーインジケーター: スクロール位置に応じて進捗状況を示すプログレスバー。
ドットインジケーターの実装例:
import 'package:flutter/material.dart';
class MyPageView extends StatefulWidget {
@override
_MyPageViewState createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
final PageController _pageController = PageController();
int _currentPage = 0;
final int _numPages = 3; // 総ページ数
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dot Indicator Example'),
),
body: Column(
children: [
Expanded(
child: PageView(
controller: _pageController,
children: [
Container(
color: Colors.red,
child: Center(child: Text('Page 1')),
),
Container(
color: Colors.green,
child: Center(child: Text('Page 2')),
),
Container(
color: Colors.blue,
child: Center(child: Text('Page 3')),
),
],
onPageChanged: (int page) {
setState(() {
_currentPage = page;
});
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _buildPageIndicator(),
),
SizedBox(height: 16.0),
],
),
);
}
List<Widget> _buildPageIndicator() {
List<Widget> list = [];
for (int i = 0; i < _numPages; i++) {
list.add(i == _currentPage ? _indicator(true) : _indicator(false));
}
return list;
}
Widget _indicator(bool isActive) {
return AnimatedContainer(
duration: Duration(milliseconds: 150),
margin: EdgeInsets.symmetric(horizontal: 8.0),
height: 8.0,
width: isActive ? 24.0 : 8.0,
decoration: BoxDecoration(
color: isActive ? Colors.blue : Colors.grey,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
);
}
}
解説:
-
_numPages
変数で総ページ数を定義しています。 -
_buildPageIndicator()
関数で、ドットインジケーターを生成しています。 -
_indicator()
関数で、アクティブなドットと非アクティブなドットの見た目を定義しています。isActive
の値に応じて、ドットの色と幅を変えています。 -
onPageChanged
内で_currentPage
を更新し、インジケーターを再描画しています。
ポイント:
-
AnimatedContainer
を使うことで、ドットのサイズや色をスムーズにアニメーションさせることができます。 - インジケーターのデザインは、アプリのテーマに合わせて調整できます。
- より複雑なインジケーター(例: プログレスバー)を実装することも可能です。
インジケーターを適切に実装することで、ユーザーは現在位置を把握しやすくなり、より快適にアプリを利用できます。次のセクションでは、onPageChanged
を使って無限スクロールを実装する方法について見ていきましょう。
無限スクロール(またはループスクロール)は、リストの終端に到達してもスクロールが止まらず、先頭に戻ってループする機能です。PageViewで無限スクロールを実現するには、onPageChanged
とPageController
を組み合わせて、擬似的に無限にスクロールしているように見せかけます。
基本的な考え方:
- リストの複製: 元のリストを複数回繰り返したリストを作成します。
- 初期位置の調整: スクロール開始位置をリストの中央付近に設定します。
-
onPageChanged
での位置調整:onPageChanged
で、リストの先頭または末尾に近づいた場合に、PageControllerを使ってリストの中央付近に移動させます。
実装例:
import 'package:flutter/material.dart';
class InfinitePageView extends StatefulWidget {
@override
_InfinitePageViewState createState() => _InfinitePageViewState();
}
class _InfinitePageViewState extends State<InfinitePageView> {
final PageController _pageController = PageController(initialPage: _initialPage);
List<int> _items = [1, 2, 3, 4, 5];
late List<int> _infiniteItems;
static const int _initialPage = 1000; // 初期ページ
static const int _multiplier = 10000; // リストの繰り返し回数
@override
void initState() {
super.initState();
_infiniteItems = List.generate(_items.length * _multiplier, (index) => _items[index % _items.length]);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Infinite Scroll Example'),
),
body: PageView.builder(
controller: _pageController,
itemCount: _infiniteItems.length,
itemBuilder: (context, index) {
return Container(
color: index % _items.length == 0 ? Colors.red : (index % _items.length == 1 ? Colors.green : (index % _items.length == 2 ? Colors.blue : (index % _items.length == 3 ? Colors.yellow : Colors.orange))),
child: Center(child: Text('Page ${_infiniteItems[index]}')),
);
},
onPageChanged: (int page) {
// 先頭または末尾に近づいたら中央に戻る
if (page <= 10) {
_jumpToCenter();
} else if (page >= _infiniteItems.length - 10) {
_jumpToCenter();
}
},
),
);
}
void _jumpToCenter() {
Future.microtask(() {
_pageController.jumpToPage(_initialPage);
});
}
}
解説:
-
_items
: 元のリストです。 -
_infiniteItems
:_items
を_multiplier
回繰り返したリストです。 -
_initialPage
:PageController
の初期ページです。リストの中央付近に設定しています。 -
onPageChanged
: 現在のページがリストの先頭または末尾に近づいた場合に、_jumpToCenter()
を呼び出して、リストの中央付近に戻します。 -
_jumpToCenter()
:PageController.jumpToPage()
を使って、指定されたページに瞬時に移動します。Future.microtask()
で囲むことで、現在のフレームが完了した後に実行されるようにスケジュールし、スムーズな動作を保証します。
ポイント:
-
PageView.builder
を使用することで、大量のページを効率的に生成できます。 -
_multiplier
の値を大きくすることで、より長い無限スクロールを実現できます。 -
_initialPage
の値を適切に設定することで、スクロール開始位置を調整できます。 -
onPageChanged
の条件式を調整することで、リストの端に近づいたときに中央に戻るタイミングを調整できます。 -
_jumpToCenter()
内でanimateToPage()
を使用することもできますが、アニメーションが発生するため、ユーザーがスクロールしている感覚が途切れてしまう可能性があります。jumpToPage()
の方がスムーズな無限スクロールを実現できます。
無限スクロールを実装することで、ユーザーはコンテンツを途切れることなく閲覧し続けることができます。次のセクションでは、onPageChanged
を使ったパフォーマンス最適化について見ていきましょう。
onPageChanged
内でsetState()
を呼び出すことは、UIの再描画をトリガーするため、パフォーマンスに影響を与える可能性があります。特に、複雑なウィジェットや大量のデータを扱う場合、setState()
の呼び出し回数を最小限に抑えることが重要です。
setState() の利用を避ける/削減する方法:
-
値の変更が必要な部分のみsetState() で囲む:
UI全体を
setState()
で囲むのではなく、実際に値が変更される部分のみをsetState()
で囲むようにします。これにより、不要なウィジェットの再描画を防ぐことができます。 -
Provider、Riverpod、Blocなどの状態管理ライブラリの利用:
状態管理ライブラリを使用することで、UIとロジックを分離し、より効率的な状態管理を実現できます。これらのライブラリは、変更された状態に関連するウィジェットのみを再描画するため、
setState()
の利用を大幅に削減できます。 -
ValueNotifier/ChangeNotifier の利用:
シンプルな状態管理であれば、
ValueNotifier
やChangeNotifier
を使用することもできます。これらのクラスは、値が変更されたときにリスナーに通知し、必要な部分のみを再描画することができます。 -
shouldRepaint を使用した CustomPainter の最適化:
CustomPainter
を使用している場合、shouldRepaint
メソッドを実装することで、再描画が必要な場合にのみpaint
メソッドが呼び出されるように制御できます。 -
const キーワードの活用:
変更されないウィジェットや変数は
const
キーワードで宣言することで、再構築を防ぐことができます。
具体的な例:ドットインジケーターの最適化
前のセクションで紹介したドットインジケーターの例を最適化してみましょう。
import 'package:flutter/material.dart';
class MyPageView extends StatefulWidget {
@override
_MyPageViewState createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
final PageController _pageController = PageController();
final ValueNotifier<int> _currentPage = ValueNotifier<int>(0); // ValueNotifierを使用
final int _numPages = 3;
@override
void dispose() {
_currentPage.dispose(); // ValueNotifierの破棄
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dot Indicator Example (Optimized)'),
),
body: Column(
children: [
Expanded(
child: PageView(
controller: _pageController,
children: [
Container(
color: Colors.red,
child: Center(child: Text('Page 1')),
),
Container(
color: Colors.green,
child: Center(child: Text('Page 2')),
),
Container(
color: Colors.blue,
child: Center(child: Text('Page 3')),
),
],
onPageChanged: (int page) {
_currentPage.value = page; // ValueNotifierの値を更新
},
),
),
ValueListenableBuilder<int>( // ValueListenableBuilderを使用
valueListenable: _currentPage,
builder: (context, value, child) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _buildPageIndicator(value),
);
},
),
SizedBox(height: 16.0),
],
),
);
}
List<Widget> _buildPageIndicator(int currentPage) { // 引数にcurrentPageを追加
List<Widget> list = [];
for (int i = 0; i < _numPages; i++) {
list.add(i == currentPage ? _indicator(true) : _indicator(false));
}
return list;
}
Widget _indicator(bool isActive) {
return AnimatedContainer(
duration: Duration(milliseconds: 150),
margin: EdgeInsets.symmetric(horizontal: 8.0),
height: 8.0,
width: isActive ? 24.0 : 8.0,
decoration: BoxDecoration(
color: isActive ? Colors.blue : Colors.grey,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
);
}
}
変更点:
-
_currentPage
をint
型からValueNotifier<int>
型に変更しました。 -
onPageChanged
内で、_currentPage.value = page;
のようにValueNotifier
の値を更新します。 -
Row
ウィジェットをValueListenableBuilder
で囲みました。ValueListenableBuilder
は、_currentPage
の値が変更されたときにのみ、内部のbuilder
関数を呼び出します。 -
_buildPageIndicator
関数にcurrentPage
を引数として渡すように変更しました。
利点:
-
onPageChanged
内でsetState()
を呼び出す必要がなくなりました。 - ドットインジケーターのみが再描画されるため、パフォーマンスが向上します。
まとめ:
onPageChanged
内でsetState()
を最小限に抑えることで、アプリのパフォーマンスを向上させることができます。状態管理ライブラリやValueNotifier
などのツールを活用し、効率的な状態管理を心がけましょう。次のセクションでは、onPageChanged
の活用事例について見ていきましょう。
onPageChanged
は、PageViewを使った様々なUIデザインにおいて、中心的な役割を果たすことができます。ここでは、代表的な活用事例をいくつかご紹介します。
1. チュートリアル画面:
アプリの初回起動時や、新機能が追加された際に、使い方を説明するチュートリアル画面で、onPageChanged
は非常に有効です。
-
実装: 各ページに説明文や画像を配置し、
onPageChanged
で現在のページ番号を取得します。 -
活用:
- 最後のページに到達したら「完了」ボタンを表示する。
- ページインジケーターで進捗状況を視覚的に示す。
- ページ遷移に合わせてアニメーションを付与し、インタラクティブな体験を提供する。
2. ステップ表示 (ウィザード形式):
フォームへの入力や設定などを、複数のステップに分けて表示するウィザード形式のUIで、onPageChanged
を活用できます。
-
実装: 各ページに異なる入力フォームや設定項目を配置し、
onPageChanged
で現在のステップ番号を取得します。 -
活用:
- 「次へ」「戻る」ボタンを配置し、
PageController
を使ってページを遷移させる。 - 現在のステップに応じて、ボタンの有効/無効を切り替える。
- ステップインジケーターで現在のステップと全体の進捗状況を示す。
- 各ステップの入力内容を検証し、エラーがある場合は次のステップに進めないように制御する。
- 「次へ」「戻る」ボタンを配置し、
3. スワイプ操作:
写真ギャラリーや商品リストなど、スワイプ操作でコンテンツを切り替えるUIで、onPageChanged
を活用できます。
-
実装: 各ページに画像や商品情報を配置し、
onPageChanged
で現在のページ番号を取得します。 -
活用:
- ページ遷移に合わせて、AppBarのタイトルを更新する。
- 関連する情報を表示/非表示する。
- ページの読み込み状況を監視し、必要な場合にのみコンテンツを読み込む。
- スワイプ操作を検知して、特定のアクションを実行する (例: お気に入り登録、商品の詳細表示)。
4. タブ切り替え:
TabViewウィジェットの代わりに、PageViewとonPageChanged
を使って、タブ切り替えを実装することもできます。
-
実装: 各ページに異なるタブの内容を配置し、
onPageChanged
で選択されたタブのインデックスを取得します。 -
活用:
- カスタムタブバーを実装し、
PageController
を使ってページを遷移させる。 - タブの選択状態を視覚的に示す。
- タブごとに異なるアニメーションを付与する。
- カスタムタブバーを実装し、
コード例 (タブ切り替え):
import 'package:flutter/material.dart';
class TabPageView extends StatefulWidget {
@override
_TabPageViewState createState() => _TabPageViewState();
}
class _TabPageViewState extends State<TabPageView> {
final PageController _pageController = PageController();
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Tab PageView Example'),
),
body: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildTabItem(0, 'Tab 1'),
_buildTabItem(1, 'Tab 2'),
_buildTabItem(2, 'Tab 3'),
],
),
Expanded(
child: PageView(
controller: _pageController,
children: [
Container(color: Colors.red, child: Center(child: Text('Tab 1 Content'))),
Container(color: Colors.green, child: Center(child: Text('Tab 2 Content'))),
Container(color: Colors.blue, child: Center(child: Text('Tab 3 Content'))),
],
onPageChanged: (int index) {
setState(() {
_selectedIndex = index;
});
},
),
),
],
),
);
}
Widget _buildTabItem(int index, String label) {
return GestureDetector(
onTap: () {
_pageController.animateToPage(
index,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
label,
style: TextStyle(
fontWeight: _selectedIndex == index ? FontWeight.bold : FontWeight.normal,
color: _selectedIndex == index ? Colors.blue : Colors.black,
),
),
),
);
}
}
まとめ:
onPageChanged
は、PageViewを使ったUIデザインにおいて、柔軟性と表現力を高めるための強力なツールです。これらの活用事例を参考に、onPageChanged
を効果的に活用して、より洗練されたFlutterアプリを開発してください。最後のセクションでは、本記事の内容をまとめます。
本記事では、FlutterのonPageChanged
コールバック関数について、その基本的な概念から、実装方法、パフォーマンス最適化、そして具体的な活用事例まで、幅広く解説してきました。onPageChanged
は、PageView
ウィジェットにおけるページ遷移を検知し、UIの更新やアニメーションの制御、ユーザーへのフィードバックなど、様々な処理を行うための重要なツールです。
主なポイント:
-
PageView
とPageController
の理解:PageView
でコンテンツをページとして表示し、PageController
でスクロールを制御します。 -
onPageChanged
の基本的な実装: ページが切り替わった際に実行されるコールバック関数で、現在のページ番号を取得できます。 - アニメーションとの連携: ページ遷移に合わせてスムーズなアニメーションを付与し、ユーザーエクスペリエンスを向上させます。
- インジケーターの実装: 現在のページ位置を視覚的に示すインジケーターを実装し、ユーザーがコンテンツの全体像を把握しやすくします。
- 無限スクロールの実装: リストを繰り返し表示することで、途切れることのないコンテンツ閲覧体験を提供します。
-
パフォーマンス最適化:
setState()
の利用を最小限に抑え、アプリのパフォーマンスを向上させます。 -
活用事例: チュートリアル画面、ステップ表示、スワイプ操作など、様々なUIデザインに
onPageChanged
を活用できます。
onPageChanged
を活用することで、以下のメリットが得られます:
- インタラクティブ性の向上: ページ遷移に合わせてUIを動的に変化させることで、ユーザーの操作に対するレスポンスを高めます。
- 視覚的な分かりやすさの向上: インジケーターやアニメーションを活用することで、ユーザーがコンテンツの状況を直感的に理解できるようにします。
- スムーズな操作性の実現: アニメーションや状態管理を適切に行うことで、快適なページング体験を提供します。
今後の学習:
- より複雑なアニメーションの実装に挑戦してみましょう。
- 状態管理ライブラリ (Provider, Riverpod, Blocなど) を利用して、
onPageChanged
と連携させた状態管理を実践してみましょう。 -
PageView
以外のスクロール可能なウィジェット (ListView
,GridView
) でも、同様の概念を応用できる場合があります。 - Flutter公式ドキュメントやサンプルコードを参考に、さらに高度なテクニックを習得しましょう。
onPageChanged
を効果的に活用することで、より洗練された、使いやすいFlutterアプリを開発することができます。本記事が、あなたのFlutter開発の一助となれば幸いです。