TabBarView with variable height inside a ListView

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      home: Home(),
    );
  }
}

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return HomeState();
  }
}

class HomeState extends State<Home> with SingleTickerProviderStateMixin {
  TabController tabController;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    tabController = TabController(length: 3, vsync: this, initialIndex: 0);
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      body: ListView(
        children: <Widget>[
          DummySection(height: 100.0,color: Colors.red,),
          DummySection(height: 150.0,color: Colors.yellow,),
          Container(
            height: 350.0,
            color: Colors.blue,
            child: Column(
              children: <Widget>[
                TabBar(
                  unselectedLabelColor: Colors.blue[100],
                  indicator: BoxDecoration(
                    color: Colors.lightBlue
                  ),
                  controller: tabController,
                  tabs: <Widget>[
                    Tab(text: "Home",),
                    Tab(text: "Fav",),
                    Tab(text: "Star",)
                  ],
                ),
                Expanded(
                  child: TabBarView(
                    controller: tabController,
                      children: [
                        DummyList(),
                        DummyList(),
                        DummyList()
                      ]
                  ),
                )
              ],
            ),
          ),
          DummySection(height: 100.0,color: Colors.red,),
          DummySection(height: 100.0,color: Colors.pink,)
        ],
      ),
    );
  }
}

// Dummy List Container
class DummySection extends StatelessWidget{
  Color color;
  double height;
  DummySection({this.color,this.height});

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: color,
      height: height,
    );
  }
}
// Dummy Listing for tab
class DummyList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView(
        children: <Widget>[
          Card(
            child: Container(
              height: 200.0,
              alignment: Alignment.center,
              child: Text("hello"),
            ),
          ),
          Card(
            child: Container(
              height: 200.0,
              alignment: Alignment.center,
              child: Text("hello"),
            ),
          ),
        ],
    );
  }
}


Don't use TabBarView, use IndexedStack with Visibility instead.

import 'package:flutter/material.dart';

final Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  TabController tabController;
  int selectedIndex = 0;

  @override
  void initState() {
    super.initState();

    tabController = TabController(
      initialIndex: selectedIndex,
      length: 2,
      vsync: this,
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: ListView(
          children: [
            Container(
              height: 128,
              color: Colors.blue,
            ),
            Container(
              height: 256,
              color: Colors.green,
            ),
            TabBar(
              tabs: <Tab>[
                Tab(text: 'Tab Left'),
                Tab(text: 'Tab Right'),
              ],
              controller: tabController,
              onTap: (int index) {
                setState(() {
                  selectedIndex = index;
                  tabController.animateTo(index);
                });
              },
            ),
            Divider(height: 0),
            IndexedStack(
              children: <Widget>[
                Visibility(
                  child: Container(
                    height: 200,
                    color: Colors.yellow,
                    child: Center(
                      child: Text('Content left'),
                    ),
                  ),
                  maintainState: true,
                  visible: selectedIndex == 0,
                ),
                Visibility(
                  child: Container(
                    height: 1000,
                    color: Colors.red,
                    child: Center(
                      child: Text('Content right'),
                    ),
                  ),
                  maintainState: true,
                  visible: selectedIndex == 1,
                ),
              ],
              index: selectedIndex,
            ),
          ],
        ),
      ),
    );
  }
}

IndexedStack will show a single child from a list of children based on index while Visibility will maintain to show or hide view. When the view is hiding, so there is no excess white space to show (stack height is equal to the maximum height of its children).

Here is the dartpad https://dartpad.dev/535f06aa01257b049c7f2f9c719c9881.


You don't need to have TabView to show Tabs content. the minus of this approcach that you are loosing animations and swipes, so you will need to do it by your self if you realy will need it.

enter image description here

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  final List<Widget> myTabs = [
    Tab(text: 'one'),
    Tab(text: 'two'),
    Tab(text: 'three'),
  ];

  TabController _tabController;

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  void initState() {
    _tabController = TabController(length: 3, vsync: this);
    _tabController.addListener(_handleTabSelection);
    super.initState();
  }

  _handleTabSelection() {
    if (_tabController.indexIsChanging) {
      setState(() {});
    }
  }

  _listItem() {
    return Container(
      decoration: BoxDecoration(
        border: Border.all(
          width: 1,
          color: Colors.blueAccent,
        ),
      ),
      height: 120,
      child: Center(
        child: Text('List Item', style: TextStyle(fontSize: 20.0)),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: <Widget>[
          _listItem(),
          TabBar(
            controller: _tabController,
            labelColor: Colors.redAccent,
            tabs: myTabs,
          ),
          Center(
            child: [
              Text('first tab'),
              Column(
                children: [
                  Text('second tab'),
                  ...List.generate(10, (index) => Text('line: $index'))
                ],
              ),
              Column(
                children: [
                  Text('third tab'),
                  ...List.generate(20, (index) => Text('line: $index'))
                ],
              ),
            ][_tabController.index],
          ),
          _listItem(),
          _listItem(),
        ],
      ),
    );
  }
}

Tags:

Flutter