📌
FlutterのTabBarのデザインをカスタマイズしたい
FlutterでTabBar
を使うことが結構あるのですが、デザインのカスタマイズをどこまで頑張れるか実験してみました。
PageView
を使うような記事もあったのですが、今回はTabBar
を使うことにこだわってみました。
TabBar
を使うと、TabView
を横スワイプして、タブを切り替えていくときに、自動的にTabBar
のスクロールもついてきてくれるからです。
PageView
を使うと、その辺も自前で実装作る必要があり、少し面倒です。
(TabBar代わりのListView
にScrollController
つけて、各タブにGlobalKey
つけて、index
変更時にスクロールさせれば良いのかな?)
できたもの
UIをカスタマイズできるTabBarを作る
コード
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class CustomTabBarPage extends HookWidget {
const CustomTabBarPage({super.key});
Widget build(BuildContext context) {
final tabLabels = ['Tab1', 'Tab2', 'Tab3', 'Tab4', 'Tab5', 'Tab6'];
final tabController = useTabController(initialLength: tabLabels.length);
final selectedIndex = useState(0);
useEffect(() {
tabController.addListener(() {
selectedIndex.value = tabController.index;
});
return null;
}, []);
return Scaffold(
appBar: AppBar(title: const Text('Custom TabBar')),
body: Column(
children: [
TabBar(
controller: tabController,
tabAlignment: TabAlignment.start,
isScrollable: true,
padding: const EdgeInsets.symmetric(horizontal: 20),
indicator: const BoxDecoration(color: Colors.transparent),
dividerColor: Colors.transparent,
labelPadding: EdgeInsets.zero,
tabs:
tabLabels.indexed.toList().map((indexed) {
final index = indexed.$1;
final tabLabel = indexed.$2;
return Material(
color: Colors.transparent,
child: InkWell(
splashFactory: NoSplash.splashFactory,
overlayColor: WidgetStateProperty.resolveWith<Color?>((
Set<WidgetState> states,
) {
return states.contains(WidgetState.focused)
? null
: Colors.transparent;
}),
onTap: () {
// ここは何もしない
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Ink(
decoration: BoxDecoration(
border:
selectedIndex.value == index
? Border.all(color: Colors.blue)
: Border.all(),
borderRadius: BorderRadius.circular(10),
color:
selectedIndex.value == index
? Colors.blue
: Colors.white,
),
child: InkWell(
onTap: () {
tabController.animateTo(index);
},
borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 12,
),
child: Text(
tabLabel,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color:
selectedIndex.value == index
? Colors.white
: Colors.black,
),
),
),
),
),
),
),
);
}).toList(),
),
Expanded(
child: TabBarView(
controller: tabController,
children: const [
Center(child: Text('タブ1')),
Center(child: Text('タブ2')),
Center(child: Text('タブ3')),
Center(child: Text('タブ4')),
Center(child: Text('タブ5')),
Center(child: Text('タブ6')),
],
),
),
],
),
);
}
}
ポイント
- TabBarの
labelPadding
はゼロにする
TabBar(
labelPadding: EdgeInsets.zero,
-
InkWell
をInkWell
で囲む- 外側の
InkWell
は、onTap
を何もしない - 外側の
InkWell
をつけることにより、padding: const EdgeInsets.symmetric(horizontal: 10)
の部分が押せてしまうのを回避
- 外側の
tabs:
tabLabels.indexed.toList().map((indexed) {
final index = indexed.$1;
final tabLabel = indexed.$2;
return Material(
color: Colors.transparent,
child: InkWell(
splashFactory: NoSplash.splashFactory,
overlayColor: WidgetStateProperty.resolveWith<Color?>((
Set<WidgetState> states,
) {
return states.contains(WidgetState.focused)
? null
: Colors.transparent;
}),
onTap: () {
// ここは何もしない
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10), // ここでPadding決める
child: Ink(
child: InkWell(
onTap: () {
tabController.animateTo(index);
},
),
),
),
),
);
}).toList(),
まとめ
TabBar
自体で、もうちょっとデザインをカスタマイズしやすくしてもらいたいな...
Discussion