フリック操作可能なBottomNavigationBar
はじめに
PageView
とBottomNavigationBar
を組み合わせて、フリック操作でページを移動できるボトムナビゲーションバーを作成するためのソースコードと解説を紹介します。
対象読者
-
BottomNavigationBar
をはじめて使う方 -
PageView
とBottomNavigationBar`を組み合わせる方法を知りたい方
本記事の内容
-
BottomNavigationBarの基本的な使い方
BottomNavigationBar
の基本構造や使用例を紹介します。 -
PageViewとBottomNavigationBarを組み合わせる方法
PageView
とBottomNavigationBar
を連携させて、タブの切り替えとページ移動を同期させる方法を解説します。 -
[応用] BottomNavigationBarの一部にPageViewを組み合わせる方法
BottomNavigationBar
の特定のタブにPageView
を組み込み、フリック移動に制限をかける方法を紹介します。
1. BottomNavigationBarの基本的な使い方
BottomNavigationBar
は、アプリケーションの画面下部に複数のアイコンを表示し、それぞれのアイコンをタップすることでページを切り替えることができるFlutterのウィジェットです。ここでは、4つのタブ(Home
, Search
, Favorites
, Profile
)を持つ基本的なBottomNavigationBar
の使い方を紹介します。
ソースコード
import 'package:flutter/material.dart';
import 'package:flutter_application/bottom_nav.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: BottomNav(),
);
}
}
import 'package:flutter/material.dart';
class BottomNav extends StatefulWidget {
const BottomNav({super.key});
State<BottomNav> createState() => _BottomNavState();
}
class _BottomNavState extends State<BottomNav> {
// 現在選択されているタブのインデックス
int _selectedIndex = 0;
// 各タブに対応するウィジェット
static const List<Widget> _pages = <Widget>[
Center(child: Text('Home Page')),
Center(child: Text('Search Page')),
Center(child: Text('Favorites Page')),
Center(child: Text('Profile Page')),
];
// タップされたときに呼ばれる関数
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: const Text('BottomNavigationBar Example'),
),
body: child: _pages[_selectedIndex], // 選択されたページを表示
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed, // 各タブの幅を固定
currentIndex: _selectedIndex, // 選択されたタブのインデックス
backgroundColor: Colors.white, // 背景色
selectedItemColor: Colors.blue, // 選択されたタブの色
unselectedItemColor: Colors.grey, // 選択されていないタブの色
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite),
label: 'Favorites',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
onTap: _onItemTapped, // タップされたときに呼ばれる関数
),
);
}
}
解説
-
BottomNavigationBarItemの設定
BottomNavigationBarItem
で、各タブのアイコンとラベルを定義しています。今回は、Icons.home
、Icons.search
、Icons.favorite
、Icons.person
という4つのアイコンを使用しています。 -
選択されたタブに応じたページの切り替え
_selectedIndex
という変数で現在選択されているタブを管理し、_onItemTapped
関数でタップされたタブのインデックスを更新しています。_pages[_selectedIndex]
で、選択されたタブに対応するページが動的に表示されます。
2. PageViewとBottomNavigationBarを組み合わせる方法
BottomNavigationBar
にPageView
を組み合わせることで、タップによるタブの切り替えだけでなく、ページをフリックして移動することができるようになります。
ソースコード
main.dartに変更はありません。
import 'package:flutter/material.dart';
class BottomNav extends StatefulWidget {
const BottomNav({super.key});
State<BottomNav> createState() => _BottomNavState();
}
class _BottomNavState extends State<BottomNav> {
// 現在選択されているタブのインデックス
int _selectedIndex = 0;
+ // PageControllerを作成
+ final PageController _pageController = PageController();
// 各タブに対応するウィジェット
static const List<Widget> _pages = <Widget>[
Center(child: Text('Home Page')),
Center(child: Text('Search Page')),
Center(child: Text('Favorites Page')),
Center(child: Text('Profile Page')),
];
// タブがタップされたときに呼ばれる関数
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
+ // PageViewのページをアニメーション付きで切り替え
+ _pageController.jumpToPage(index);
}
+ // ページがスワイプされたときにタブのインデックスを更新
+ void _onPageChanged(int index) {
+ setState(() {
+ _selectedIndex = index;
+ });
+ }
+
+ void dispose() {
+ _pageController.dispose();
+ super.dispose();
+ }
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: const Text('BottomNavigationBar Example'),
),
- body: child: _pages[_selectedIndex], // 選択されたページを表示
+ // PageViewを追加
+ body: PageView(
+ controller: _pageController, // PageControllerを設定
+ onPageChanged: _onPageChanged, // ページが変更されたときにタブのインデックスを更新
+ children: _pages, // 各ページのウィジェットを表示
+ ),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed, // 各タブの幅を固定
currentIndex: _selectedIndex, // 選択されたタブのインデックス
backgroundColor: Colors.white, // 背景色
selectedItemColor: Colors.blue, // 選択されたタブの色
unselectedItemColor: Colors.grey, // 選択されていないタブの色
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite),
label: 'Favorites',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
onTap: _onItemTapped, // タップされたときに呼ばれる関数
),
);
}
}
解説
-
PageViewの追加
PageView
は、children
に指定した画面をスワイプで切り替えられるウィジェットです。今回のコードでは、children
に_pages
(Home
,Search
,Favorites
,Profile
)を設定しています。 -
ページとタブの連動
ページの移動をPageController
を使って制御するようにコードを変更します。BottomNavigationBar
のタブがタップされたとき、_onItemTapped
関数が呼ばれ、_selectedIndex
を更新し、PageView
が対応するページにアニメーション付きで移動します。逆に、ページがスワイプされたときには_onPageChanged
関数が呼ばれ、タブのインデックスも更新されるようにしています。
3. [応用] BottomNavigationBarの一部にPageViewを組み合わせる方法
この応用例では、BottomNavigationBar
のHome
タブはタップでのみ移動可能とし、Search
, Favorites
, Profile
の3つのタブはタップとフリックの両方でページを切り替えられるようにします。
ソースコード
main.dartに変更はありません。
import 'package:flutter/material.dart';
class BottomNav extends StatefulWidget {
const BottomNav({super.key});
State<BottomNav> createState() => _BottomNavState();
}
class _BottomNavState extends State<BottomNav> {
int _selectedIndex = 0;
final PageController _pageController = PageController();
// 各タブに対応するウィジェット
static const List<Widget> _pages = <Widget>[
Center(child: Text('Home Page')),
Center(child: Text('Search Page')),
Center(child: Text('Favorites Page')),
Center(child: Text('Profile Page')),
];
// タブがタップされたときに呼ばれる関数
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
- // PageViewのページをアニメーション付きで切り替え
- _pageController.jumpToPage(index);
+ // Homeから他のページへの遷移
+ if (_selectedIndex == 0) {
+ _pageController.jumpToPage(0); // Homeページへの遷移
+ } else {
+ _pageController.jumpToPage(index); // 他のページへの遷移
+ }
}
// ページがスワイプされたときにタブのインデックスを更新
void _onPageChanged(int index) {
setState(() {
- _selectedIndex = index;
+ // SearchからHomeへの移動は許可しない
+ if (index == 0 && _selectedIndex == 1) {
+ _pageController.jumpToPage(1); // Searchページに留まる
+ } else {
+ _selectedIndex = index; // それ以外はインデックスを更新
+ }
});
}
void dispose() {
_pageController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: const Text('BottomNavigationBar Example'),
),
body: PageView(
controller: _pageController,
onPageChanged: _onPageChanged,
+ physics: _selectedIndex == 0
+ ? const NeverScrollableScrollPhysics() // Homeページではフリック移動を無効
+ : const BouncingScrollPhysics(), // 他のページではフリック移動を有効
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _selectedIndex,
backgroundColor: Colors.white,
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite),
label: 'Favorites',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
onTap: _onItemTapped,
),
);
}
}
解説
-
フリック移動の制御
- physicsプロパティを使い、
Home
タブはフリック移動を無効とし、Search
,Favorites
,Profile
はフリック移動が可能に設定しています。ただしこれだけではHome
からSearch
のフリック移動は禁止できますが、Search
からHome
へのフリック移動はできてしまいます。 - onPageChangedメソッド内でインデックスの値に応じて、特定の条件下でのみフリックによるページ移動を許可します。
if (index == 0 && _selectedIndex == 1)
の_selectedIndex
は現在のインデックス、index
は遷移先のインデックスを表すため、現在Search
(_selectedIndex == 1)にいて、フリックでHome
(index == 0)に移動しようとした際に同じページ(Search
)に遷移させることでフリック移動を制限しています(半ば無理やりですが、、、)。
- physicsプロパティを使い、
まとめ
今回はBottomNavigationBar
とPageView
を組み合わせる方法について紹介しました。さらなる応用として、Home
タブではBottomNavigationBar
を表示しないでSearch
, Favorites
, Profile
でのみBottomNavigationBar
を表示する、といったこともできそうです。
Discussion