![[Flutter] 上タブ(TabBar)を使いたい](https://images.ctfassets.net/ct0aopd36mqt/wp-thumbnail-97bd004eb227348cf028ece41fd4689e/b36c0bd625924c92c33ad88396cb5f71/flutter.png)
[Flutter] 上タブ(TabBar)を使いたい
こんにちは。きんくまです。
今回は上タブ(TabBar)を使ってみようと思い調べてみました。
作ったもの
iOS | Android | |
---|---|---|
テキスト | ![]() |
![]() |
アイコン画像 | ![]() |
![]() |
ソースコード(全文)
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.light(),
scaffoldBackgroundColor: Colors.white,
appBarTheme: AppBarTheme(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
tabBarTheme: TabBarThemeData(
labelColor: Colors.blue, // 選択されたタブの色
unselectedLabelColor: Colors.grey, // 選択されていないタブの色
//indicatorColor: Colors.blue, // indicatorで設定するので不要
indicatorSize: TabBarIndicatorSize.tab,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(color: Colors.blue, width: 2.0),
insets: EdgeInsets.symmetric(horizontal: 8.0),
),
dividerColor: const Color.fromRGBO(231, 231, 231, 1),
dividerHeight: 1,
),
),
home: TabScreen(),
);
}
}
enum TabScreenTabType {
home,
search,
profile;
static TabScreenTabType fromTabIndex(int index) {
if (index < 0 || index > values.length - 1) {
throw ArgumentError('Invalid tab index: $index');
}
return values[index];
}
String get title {
switch (this) {
case TabScreenTabType.home:
return 'ホーム';
case TabScreenTabType.search:
return '検索';
case TabScreenTabType.profile:
return 'プロフィール';
}
}
}
class TabScreen extends StatefulWidget {
const TabScreen({super.key});
State<TabScreen> createState() => _TabScreenState();
}
class _TabScreenState extends State<TabScreen> with TickerProviderStateMixin {
late TabController _tabController;
void initState() {
super.initState();
_setUpTabController();
}
void _setUpTabController() {
_tabController = TabController(
length: TabScreenTabType.values.length,
vsync: this,
);
_tabController.addListener(() {
if (_tabController.indexIsChanging) {
// タブの切り替えが始まったとき
// タブをタップしたときのみ呼ばれる
print(
'タブがこれから切り替わります: index ${_tabController.previousIndex} -> ${_tabController.index}',
);
} else {
// タブの切り替え完了。
// タブをタップしたときも、スワイプで切り替えた場合も呼ばれる
print(
'タブが切り替わりました: ${TabScreenTabType.fromTabIndex(_tabController.index)}, 現在のindex: ${_tabController.index}. 前のindex ${_tabController.previousIndex}',
);
}
});
}
void dispose() {
_tabController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(context),
body: _buildTabBarView(context),
);
}
AppBar _buildAppBar(BuildContext context) {
return AppBar(
title: Text('タブ画面'),
bottom: PreferredSize(
preferredSize: Size.fromHeight(44),
child: Container(
color: Colors.white, // タブの背景色
child: TabBar(
controller: _tabController,
onTap: (index) {
print('タブがタップされたよ: $index');
},
tabs: [
// アイコンバージョン
// Tab(icon: Icon(Icons.home)),
// Tab(icon: Icon(Icons.search)),
// Tab(icon: Icon(Icons.person)),
// タイトルバージョン
Tab(text: TabScreenTabType.home.title),
Tab(text: TabScreenTabType.search.title),
Tab(text: TabScreenTabType.profile.title),
],
),
),
),
);
}
TabBarView _buildTabBarView(BuildContext context) {
return TabBarView(
controller: _tabController,
children: [
Center(child: Text('ホーム画面')),
Center(child: Text('検索画面')),
Center(child: Text('プロフィール画面')),
],
);
}
}
説明
見た目の調整
全体的な見た目はThemeDataに設定してあります。
tabBarTheme: TabBarThemeData(
labelColor: Colors.blue, // 選択されたタブの色
unselectedLabelColor: Colors.grey, // 選択されていないタブの色
//indicatorColor: Colors.blue, // indicatorで設定するので不要
indicatorSize: TabBarIndicatorSize.tab,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(color: Colors.blue, width: 2.0),
insets: EdgeInsets.symmetric(horizontal: 8.0),
),
dividerColor: const Color.fromRGBO(231, 231, 231, 1),
dividerHeight: 1,
),
高さを変更したかったのでPreferredSizeを使いました。
また背景色をナビゲーションヘッダーと違う色にしたかったのでContainerでTabBarを囲っています
AppBar _buildAppBar(BuildContext context) {
return AppBar(
title: Text('タブ画面'),
bottom: PreferredSize(
preferredSize: Size.fromHeight(44),
child: Container(
color: Colors.white, // タブの背景色
child: TabBar(
controller: _tabController,
onTap: (index) {
print('タブがタップされたよ: $index');
},
tabs: [
// アイコンバージョン
// Tab(icon: Icon(Icons.home)),
// Tab(icon: Icon(Icons.search)),
// Tab(icon: Icon(Icons.person)),
// タイトルバージョン
Tab(text: TabScreenTabType.home.title),
Tab(text: TabScreenTabType.search.title),
Tab(text: TabScreenTabType.profile.title),
],
),
),
),
);
}
タブ切り替わりイベントの制御
タブ切り替わりイベントの制御をしたかったので、TabControllerを使っています。
メモリリークがおきないように、disposeでTabControllerを開放しています。
class _TabScreenState extends State<TabScreen> with TickerProviderStateMixin {
late TabController _tabController;
void initState() {
super.initState();
_setUpTabController();
}
void _setUpTabController() {
_tabController = TabController(
length: TabScreenTabType.values.length,
vsync: this,
);
_tabController.addListener(() {
if (_tabController.indexIsChanging) {
// タブの切り替えが始まったとき
// タブをタップしたときのみ呼ばれる
print(
'タブがこれから切り替わります: index ${_tabController.previousIndex} -> ${_tabController.index}',
);
} else {
// タブの切り替え完了。
// タブをタップしたときも、スワイプで切り替えた場合も呼ばれる
print(
'タブが切り替わりました: ${TabScreenTabType.fromTabIndex(_tabController.index)}, 現在のindex: ${_tabController.index}. 前のindex ${_tabController.previousIndex}',
);
}
});
}
void dispose() {
_tabController.dispose();
super.dispose();
}
実際にタブをタップして、タブがきりかわるときのイベントのprint文です
I/flutter (27639): タブがこれから切り替わります: index 1 -> 0
I/flutter (27639): タブがタップされたよ: 0
I/flutter (27639): タブが切り替わりました: TabScreenTabType.home, 現在のindex: 0. 前のindex 1
コンテンツ部分を横にスワイプしてタブを切り替えた場合は、_tabController.indexIsChangingがfalseのときのみ呼ばれます
I/flutter (27639): タブが切り替わりました: TabScreenTabType.search, 現在のindex: 1. 前のindex 0
感想
細かいところの制御ができて良かったです。
今後は今回の上タブにgo_routerと下タブ(BottomNaigationBar)をからめたサンプルを作りたいと思っています