📌

FlutterのTabBarのデザインをカスタマイズしたい

に公開

FlutterでTabBarを使うことが結構あるのですが、デザインのカスタマイズをどこまで頑張れるか実験してみました。
PageViewを使うような記事もあったのですが、今回はTabBarを使うことにこだわってみました。

https://qiita.com/kurogoma939/items/6ea000cc0dd9799d78dd

TabBarを使うと、TabViewを横スワイプして、タブを切り替えていくときに、自動的にTabBarのスクロールもついてきてくれるからです。
PageViewを使うと、その辺も自前で実装作る必要があり、少し面倒です。
(TabBar代わりのListViewScrollControllerつけて、各タブに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,
  • InkWellInkWellで囲む
    • 外側の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