Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can flutter tabs have different widths and distribute the remaining space evenly as right and left paddings?

Tags:

flutter

dart

This is the way i tried to do it, but i obviously need the paddings to be calculated somehow. I'm thinking in a worst case scenario creating some sort of function that calculates all that, but can i select elements and get values?

This is roughly how i want them to look like (space between should vary in accordance to screen sizes) https://i.imgur.com/huhC9vn.png

I'm also thinking maybe some screens might not be able to fit it since the emulated one is pretty big, in which case i'm also thinking of having a minimum padding size and just make them scrollable.

bottom: TabBar(
  labelPadding: EdgeInsets.only(left: 8, right: 8),
  isScrollable: true,
  tabs: <Widget>[
    Tab(
      text: 'General',
    ),
    Tab(
      text: 'Financial',
    ),
    Tab(
      text: 'Experts & Participants',
    ),
  ],
)),
like image 590
morgred Avatar asked Jan 25 '23 15:01

morgred


1 Answers

The way the TabBar is implemented, doesn't allow to set flexible tabs. So you have to calculate the horizontal padding for each tab.

You'll need to get the tab widths, so you could do this:

  • Set a GlobalKey to each Tab and then access to key.currentContext.size.width.
  • Since key.currentContext would be null during build, you need to wait until the TabBar is rendered and then get the tabs width. To do that, you could use WidgetsBinding.instance.addPostFrameCallback() inside initState.
  • If the device orientation can change, you need to calculate the padding again, so you could do that inside didChangeDependencies instead of initState.
  • After you calculate the padding, you need to store that padding in a variable and call setState to update the padding of the tabs.
  • To avoid calling setState in the entire tree, you could put the TabBar in a custom StatefulWidget.
  • To be able to do this with any TabBar in the app, you could create a custom TabBar widget, that receives the TabBar and return a new one with the calculated padding.

So, depending on your case, you could implement some or all of the points mentioned. Here is an example with all the points mentioned:

class TabBarWithFlexibleTabs extends StatefulWidget implements PreferredSizeWidget {
  TabBarWithFlexibleTabs({this.child});

  final TabBar child;

  @override
  Size get preferredSize => child.preferredSize;

  @override
  _TabBarWithFlexibleTabsState createState() => _TabBarWithFlexibleTabsState();
}

class _TabBarWithFlexibleTabsState extends State<TabBarWithFlexibleTabs> {
  final _tabs = <Widget>[];
  final _tabsKeys = <Tab, GlobalKey>{};
  var _tabsPadding = 0.0;

  void _updateTabBarPadding() => setState(() {
        final screenWidth = MediaQuery.of(context).size.width;
        final tabBarWidth = _tabsKeys.values
            .fold(0, (prev, tab) => prev + tab.currentContext.size.width);
        _tabsPadding = tabBarWidth < screenWidth
            ? ((screenWidth - tabBarWidth) / widget.child.tabs.length) / 2
            : widget.child.labelPadding?.horizontal ?? 16.0;
      });

  @override
  void initState() {
    super.initState();
    widget.child.tabs.forEach((tab) => _tabsKeys[tab] = GlobalKey());
    _tabs.addAll(widget.child.tabs
        .map((tab) => Container(key: _tabsKeys[tab], child: tab)));
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    WidgetsBinding.instance.addPostFrameCallback((_) => _updateTabBarPadding());
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: widget.child.tabs.length,
      child: TabBar(
        tabs: _tabs,
        isScrollable: true,
        labelPadding: EdgeInsets.symmetric(
          horizontal: _tabsPadding,
          vertical: widget.child.labelPadding?.vertical ?? 0,
        ),
        // TODO: pass other parameters used in the TabBar received, like this:
        controller: widget.child.controller,
        indicatorColor: widget.child.indicatorColor,
      ),
    );
  }
}

And you could use it like this:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
        bottom: TabBarWithFlexibleTabs(
          child: TabBar(
            tabs: <Widget>[
              Tab(
                text: 'General',
              ),
              Tab(
                text: 'Financial',
              ),
              Tab(
                text: 'Experts & Participants',
              ),
            ],
          ),
        )
    ),
  );
}

Notes:

  • The TabBar will be scrollable if it can't fit the screen width.
  • Check the // TODO: pass other parameters used in the TabBar received.
  • You may need to remove the DefaultTabController if you want to use a custom one.
like image 135
Pablo Barrera Avatar answered Feb 02 '23 21:02

Pablo Barrera