Search code examples
fluttertabsstackspacetabbar

Flutter Tab bar can't take all space


I want to make a custom tab widget and set a list of this widgets to a tab bar. But tab bar can't take all space and some space will remain. I try to wrap it with PreferredSize but it doesn't work . The tab bar (ScrollTabBar) :

Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Container(
            decoration: BoxDecoration(
              boxShadow: [
                BoxShadow(
                  color: Colors.black54,
                  blurRadius: 5.0,
                  offset: Offset(0.0, 0.75),
                ),
              ],
              borderRadius: BorderRadius.circular(widget.borderRadiusT),
              color: Colors.red,
            ),
            height: widget.tabHeight,
            child: PreferredSize(
              preferredSize: Size.fromHeight(widget.tabHeight),
              child: TabBar(
                indicator: UnderlineTabIndicator(
                  borderSide: BorderSide(
                    color: widget.indicatorColor,
                    width: widget.indicatorWheight,
                  ),
                  insets: EdgeInsets.symmetric(
                    horizontal: widget.horizontalPadding,
                  ),
                ),
                indicatorWeight: widget.indicatorWheight,
                indicatorColor: widget.indicatorColor,
                labelPadding: EdgeInsets.only(
                  bottom: 0,
                  right: widget.horizontalPadding,
                  left: widget.horizontalPadding,
                ),
                labelColor: widget.activeTextColor,
                unselectedLabelColor: widget.diactiveTextColor,
                controller: widget.tabController,
                tabs: widget.tabList,
                isScrollable: true,
              ),
            ),
          ),
          Expanded(
            child: TabBarView(
              controller: widget.tabController,
              children: [
                for (var builder in widget.screenList) builder.call(context)
              ],
            ),
          ),
        ],
      ),
    );
  }

tabList is list of FTabComp :


FTabComp(
  String title,
  Key key,
  ScrollTabBar parent, {
  bool haveDivider = true,
}) {
  return Tab(
    key: key,
    child: Container(
      color: Colors.blue,
      width: parent.tabLength,
      child: Stack(
        clipBehavior: Clip.none,
        children: [
          Align(
            alignment: Alignment.center,
            child: Text(title),
          ),
          haveDivider
              ? Positioned.fill(
                  left: parent.tabLength * - 1.5,
                  child: SizedBox(
                    height: double.maxFinite,
                    child: VerticalDivider(
                      color: parent.outerBackgroundColor,
                      thickness: parent.dviderWidth,
                    ),
                  ),
                )
              : Center()
          ,
        ],
      ),
    ),
  );
}

Container are red . Tabs are blue , if you solve this , I will say thank you. Image


Solution

  • To make the TabBar use the maximum width of the screen you should remove the isScrollable property or set it to false. This way the TabBar is going to fill the entire width of the screen and resize each tab accordingly. From the docs, each tab gets an equal share of the available space). Also, the tabLength should be removed as it doesn't make sense anymore.

    Now, the position of VerticalDivider should be calculated. It should take into account the screen width, the width of the tab, and also the padding between tabs. And it should be places in a Stack with the TabBar to make it the same height as the TabBar itself.

    The algorithm to calculate the tab width can be something like this:

        double width = MediaQuery.of(context).size.width;
        double tabLength = (width -
                horizontalPadding * (tabCount + 1) -
                horizontalPadding * (tabCount - 1)) /
            tabCount;
    

    Below are the screenshots of the final result. Take a look also on the live demo on DartPad (Wait a little to run):

    Small Medium Large
    enter image description here enter image description here enter image description here

    Take a look at the changed code below:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(),
          debugShowCheckedModeBanner: false,
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({Key? key}) : super(key: key);
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
      late TabController tabController;
      late TabController tabController2;
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        tabController = TabController(length: 4, vsync: this);
        tabController2 = TabController(length: 3, vsync: this);
      }
    
      @override
      Widget build(BuildContext context) {
        double horizontalPadding = 8;
    
        return Scaffold(
          body: ScrollTabBar(
            tabController: tabController,
            horizontalPadding: horizontalPadding,
            tabList: const [
              FTabComp(
                title: 'secKey',
                key: ValueKey('tab1'),
              ),
              FTabComp(
                title: 'firstKey',
                key: ValueKey('tab2'),
              ),
              FTabComp(
                title: 'otherKey',
                key: ValueKey('tab3'),
              ),
              FTabComp(
                title: 'anotherKey',
                key: ValueKey('tab4'),
              ),
            ],
            screenList: [
              (context) => const Text('Tab 1'),
              (context) => const Text('Tab 2'),
              (context) => const Text('Tab 3'),
              (context) => const Text('Tab 4'),
            ],
          ),
        );
      }
    }
    
    class ScrollTabBar extends StatefulWidget {
      final double borderRadiusT;
      final double tabHeight;
      final Color indicatorColor;
      final Color activeTextColor;
      final Color diactiveTextColor;
      final double indicatorWheight;
      final double horizontalPadding;
      final Color outerBackgroundColor;
      final double dviderWidth;
      final TabController? tabController;
      final List<Widget> tabList;
      final List<Widget Function(BuildContext)> screenList;
      final bool haveDivider;
      const ScrollTabBar({
        Key? key,
        this.borderRadiusT = 4,
        this.tabHeight = 48,
        this.indicatorColor = Colors.blue,
        this.activeTextColor = Colors.black,
        this.diactiveTextColor = Colors.white38,
        this.indicatorWheight = 8,
        this.horizontalPadding = 8,
        required this.tabController,
        required this.tabList,
        required this.screenList,
        this.outerBackgroundColor = Colors.black,
        this.dviderWidth = 4,
        this.haveDivider = true,
      }) : super(key: key);
    
      @override
      State<ScrollTabBar> createState() => _ScrollTabBarState();
    }
    
    class _ScrollTabBarState extends State<ScrollTabBar> {
      @override
      Widget build(BuildContext context) {
        double width = MediaQuery.of(context).size.width;
        double tabLength = (width -
                widget.horizontalPadding * (widget.tabList.length + 1) -
                widget.horizontalPadding * (widget.tabList.length - 1)) /
            widget.tabList.length;
    
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              Container(
                decoration: BoxDecoration(
                  boxShadow: const [
                    BoxShadow(
                      color: Colors.black54,
                      blurRadius: 5.0,
                      offset: Offset(0.0, 0.75),
                    ),
                  ],
                  borderRadius: BorderRadius.circular(widget.borderRadiusT),
                  color: Colors.red,
                ),
                height: widget.tabHeight,
                child: Stack(
                  children: [
                    TabBar(
                      indicator: UnderlineTabIndicator(
                        borderSide: BorderSide(
                          color: widget.indicatorColor,
                          width: widget.indicatorWheight,
                        ),
                        insets: EdgeInsets.symmetric(
                          horizontal: widget.horizontalPadding,
                        ),
                      ),
                      indicatorWeight: widget.indicatorWheight,
                      indicatorColor: widget.indicatorColor,
                      labelPadding: EdgeInsets.only(
                        bottom: 0,
                        right: widget.horizontalPadding,
                        left: widget.horizontalPadding,
                      ),
                      labelColor: widget.activeTextColor,
                      unselectedLabelColor: widget.diactiveTextColor,
                      controller: widget.tabController,
                      tabs: widget.tabList,
                    ),
                    for (int i = 1; i < widget.tabList.length; i++)
                      Positioned.fill(
                        left: (widget.horizontalPadding + tabLength + widget.horizontalPadding) * i - (widget.dviderWidth / 2),
                        right: width,
                        child: SizedBox(
                          height: widget.tabHeight,
                          child: VerticalDivider(
                            color: widget.outerBackgroundColor,
                            thickness: widget.dviderWidth,
                          ),
                        ),
                      ),
                  ],
                ),
              ),
              Expanded(
                child: TabBarView(
                  controller: widget.tabController,
                  children: [
                    for (var builder in widget.screenList) builder.call(context)
                  ],
                ),
              ),
            ],
          ),
        );
      }
    }
    
    class FTabComp extends StatelessWidget {
      final String title;
      const FTabComp({
        Key? key,
        required this.title,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Tab(
          key: key,
          child: Container(
            color: Colors.blue,
            child: Stack(
              clipBehavior: Clip.none,
              children: [
                Align(
                  alignment: Alignment.center,
                  child: Text(title),
                ),
              ],
            ),
          ),
        );
      }
    }