Search code examples
flutterflutter-layout

Flutter: tabs with dynamic height based on content


I got it working somehow, but the scroll feature is gone:

return Scaffold(
      body: DefaultTabController(
        length: 2,
        child: ListView(
          children: <Widget>[
            Container(
              height: 120,
              child: Center(
                child: Text('something on top'),
              ),
            ),
            TabBar(
              // controller: _tabController,
              labelColor: Colors.redAccent,
              isScrollable: true,
              tabs: [
                Tab(text: "Finished"), // TODO: translate
                Tab(text: "In progress"), // TODO: translate
              ],
            ),
            Center(
              child: [
                Text('second tab1232'),
                Text('second tab111'),
                Column(
                  children: List.generate(20, (index) => Text('line: $index'))
                      .toList(),
                ),
                Text('third tab')
              ][0], // change this
            ),
            Container(child: Text('another component')),
          ],
        ),
      ),
    );

Note: check the [0] that I simplified.

Not sure if I can fix the scroll from this or if I need to take a totally different approach.

Example of content scroll working with the original way: https://flutter.dev/docs/cookbook/design/tabs


Solution

  • With this we don't have the swipe and its animation but it could be created by some other widgets. It works with a dynamic height anyway:

    import 'package:flutter/material.dart';
    import 'package:flutter/widgets.dart';
    
    class OptionsScreen extends StatefulWidget {
      const OptionsScreen({Key? key}) : super(key: key);
      @override
      OptionsScreenState createState() => OptionsScreenState();
    }
    
    class OptionsScreenState extends State<OptionsScreen> {
      final bodyGlobalKey = GlobalKey();
      late var streamFunction;
    
      @override
      void initState() {
        streamFunction = delayFunction;
        super.initState();
      }
    
      @override
      void dispose() {
        super.dispose();
      }
    
      Stream<int> delayFunction = (() async* {
        for (int i = 1; i <= 3; i++) {
          await Future<void>.delayed(const Duration(seconds: 1));
          yield i;
        }
      })();
    
      @override
      Widget build(BuildContext context) {
        return StreamBuilder<int>(
          stream: streamFunction,
          builder: (BuildContext context, snapshot) {
            if (snapshot.hasError) return Text('Error: ${snapshot.error}');
            if (!snapshot.hasData) return const Text('loading...');
            if (snapshot.data == null) return Container();
    
            // return tabs();
            return const CustomTabs();
          },
        );
      }
    }
    
    class CustomTabs extends StatefulWidget {
      const CustomTabs({Key? key}) : super(key: key);
    
      @override
      State<CustomTabs> createState() => _CustomTabsState();
    }
    
    class _CustomTabsState extends State<CustomTabs>
        with SingleTickerProviderStateMixin {
      late TabController _tabController;
    
      @override
      void initState() {
        _tabController = TabController(length: 3, vsync: this);
        super.initState();
      }
    
      @override
      void dispose() {
        _tabController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                TabBar(
                  isScrollable: true,
                  controller: _tabController,
                  // COMMENT FOR WAY B
                  onTap: (int index) {
                    setState(() {
                      _tabController.animateTo(index);
                    });
                  },
                  labelColor: Theme.of(context).primaryColor,
                  indicator: UnderlineTabIndicator(
                    borderSide: BorderSide(
                      color: Theme.of(context).primaryColor,
                    ),
                  ),
                  tabs: [
                    Tab(text: '1'),
                    Tab(text: '2'),
                    Tab(text: '3'),
                  ],
                ),
                // WAY A
                // THIS WAY HAS NO ANIMATION BUT IT HAS DYNAMIC HEIGHT
                // SetState TRIGGERS STREAMBUILDER WITH TAB CHANGES
                Column(
                  children: [
                    Visibility(
                      visible: _tabController.index == 0,
                      child: const Text('1111'),
                    ),
                    Visibility(
                      visible: _tabController.index == 1,
                      child: Column(
                        children: const [
                          Text('2222'),
                          Text('2222'),
                          Text('2222'),
                          Text('2222'),
                          Text('2222'),
                          Text('2222'),
                        ],
                      ),
                    ),
                    Visibility(
                      visible: _tabController.index == 2,
                      child: const Text('33333'),
                    ),
                    const Text('This is a different widget. Tabs will push it down')
                  ],
                ),
                // THIS WAY HAS NO DYNAMIC HEIGHT BUT IT HAS ANIMATION
                // STREAMBUILDER ONLY TRIGGERS FIRST TIME
                // Container(
                //   color: Colors.red,
                //   height: 200,
                //   padding: const EdgeInsets.only(top: 8.0),
                //   child: TabBarView(
                //     controller: _tabController,
                //     children: [
                //       const Text('1111'),
                //       Column(
                //         children: const [
                //           Text('2222'),
                //           Text('2222'),
                //           Text('2222'),
                //           Text('2222'),
                //         ],
                //       ),
                //       const Text('3333'),
                //     ],
                //   ),
                // ),
              ],
            ),
          ],
        );
      }
    }