45

So this is my layout structure

This is the main screen

ListView(
  children: <Widget>[
    _buildCarousel(),
    _buildHomeTabBar(),
  ],
)

And this is the HomeTabBar screen

SingleChildScrollView(
  child:
    DefaultTabController(
      length: myTabs.length,
      initialIndex: 0,
      child: Column(
        children: [
          TabBar(
            isScrollable: true,
            tabs: myTabs,
          ),
          Container(
            height: 600,
              child: 
              TabBarView(
                children: [
                  ChildScreen(),
                  ChildScreen2(),
                  ChildScreen3(),
                ],
              )
          )
        ],
      ))
);

I want to get rid off that Container height. How do we do this?

The ChildScreen is getting the data from REST it is actually a GridView.Builder so the height of Container should be dynamic.

Sorry I missed layout for ChildScreen actually like this

SingleChildScrollView(
  // shrinkWrap: true,
child: Column(
    children: <Widget>[
        StreamBuilder(
          stream: categoryBloc.categoryList,
          builder: (context, AsyncSnapshot<List<Category>> snapshot){
              if (snapshot.hasData && snapshot!=null) {
                if(snapshot.data.length > 0){
                  return buildCategoryList(snapshot);
                }
                else if(snapshot.data.length==0){
                    return Text('No Data');
                }
              }
              else if (snapshot.hasError) {
                return ErrorScreen(errMessage: snapshot.error.toString());
              }     
              return Center(child: CircularProgressIndicator());

          },
        ),       
    ]
  )
);

So inside StreamBuilder is GridView.Builder. The main thing I want to remove Container height. It looks ugly on different devices...

So if I remove the height it will not show on screen and throw error

I/flutter ( 493): #2 RenderObject.layout (package:flutter/src/rendering/object.dart:1578:12) I/flutter ( 493): #3 RenderSliverList.performLayout.advance (package:flutter/src/rendering/sliver_list.dart:200:17) I/flutter ( 493): #4 RenderSliverList.performLayout (package:flutter/src/rendering/sliver_list.dart:233:19) I/flutter ( 493): #5 RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7) I/flutter ( 493): #6 RenderSliverPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:182:11) I/flutter ( 493): #7 RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7) I/flutter ( 493): #8 RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:405:13) I/flutter ( 493): #9 RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1316:12) I/flutter ( 493): #10 RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1234:20) I/flutter ( 493): #11 RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7) I/flutter ( 493): #12 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:13) I/flutter ( 493): #13 RenderObject.layout (package:flutter/src/rendering/object.dart:1634:7)

Kherel
  • 14,882
  • 5
  • 50
  • 80
stuckedunderflow
  • 3,551
  • 8
  • 46
  • 63

9 Answers9

93

I had similar task at my project. The root of the problem is that you are trying to put TabBarView (scrollable widget that trying to be as big as possible), in a column widget, that have no height.

One of solutions is get rid of scrollable widgets that wraps your TabBarView in this example ListView + Column. And use NestedScrollView instead of them. You need to put _buildCarousel() and TabBar in the headerSliverBuilder part, and TabBarView inside the body of NestedScrollView.

I don't know how is your design looks, but NestedScrollView by default opens up to 100% height of screen view. So if you want to make an effect that everything is on one screen, it easer to just block scrolling, by change ScrollController behavior when it’s needed.

In this example I’m blocking scrolling on third tab, but you can check visibility of the last item of the grid view. Just add the key to the last item, and check its position on the screen.

Hope it’s help.

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  final bodyGlobalKey = GlobalKey();
  final List<Widget> myTabs = [
    Tab(text: 'auto short'),
    Tab(text: 'auto long'),
    Tab(text: 'fixed'),
  ];
  TabController _tabController;
  ScrollController _scrollController;
  bool fixedScroll;

  Widget _buildCarousel() {
    return Stack(
      children: <Widget>[
        Placeholder(fallbackHeight: 100),
        Positioned.fill(child: Align(alignment: Alignment.center, child: Text('Slider'))),
      ],
    );
  }

  @override
  void initState() {
    _scrollController = ScrollController();
    _scrollController.addListener(_scrollListener);
    _tabController = TabController(length: 3, vsync: this);
    _tabController.addListener(_smoothScrollToTop);

    super.initState();
  }

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

  _scrollListener() {
    if (fixedScroll) {
      _scrollController.jumpTo(0);
    }
  }

  _smoothScrollToTop() {
    _scrollController.animateTo(
      0,
      duration: Duration(microseconds: 300),
      curve: Curves.ease,
    );

    setState(() {
      fixedScroll = _tabController.index == 2;
    });
  }

  _buildTabContext(int lineCount) => Container(
        child: ListView.builder(
          physics: const ClampingScrollPhysics(),
          itemCount: lineCount,
          itemBuilder: (BuildContext context, int index) {
            return Text('some content');
          },
        ),
      );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        controller: _scrollController,
        headerSliverBuilder: (context, value) {
          return [
            SliverToBoxAdapter(child: _buildCarousel()),
            SliverToBoxAdapter(
              child: TabBar(
                controller: _tabController,
                labelColor: Colors.redAccent,
                isScrollable: true,
                tabs: myTabs,
              ),
            ),
          ];
        },
        body: Container(
          child: TabBarView(
            controller: _tabController,
            children: [_buildTabContext(2), _buildTabContext(200), _buildTabContext(2)],
          ),
        ),
      ),
    );
  }
}

enter image description here .

Another way to get what you want could be creating custom TabView widget.

Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
Kherel
  • 14,882
  • 5
  • 50
  • 80
27

If you are getting error with Expanded also, then don't use TabBarView create custom TabBarView

  1. Get index from onTap, is property of TabBar.
  2. Then use if else in onTap function which index to be selected and initialize global varibale with index.
  3. Then use Builder Widgets for building your TabBarView. Again In Builder you are going to use if else to get which index selected. Then return your widget in if else.

In this case you can get maximum or minimum height of your widget. You don't need to specify container height.

_selectedTabBar is a global variable

 Column(children: [
               TabBar(
                       onTap: (index) {
                          print(index);                                  
                          setState(() {
                                _selectedTabbar = index;
                          });
                        },
                        tabs: [
                          Tab(
                            text: 'Tab1',
                          ),
                          Tab(
                            text: 'Tab2',
                          ),
                          Tab(
                            text: 'Tab3',
                          ),
                        ],
                      ),
                    
                      Builder(builder: (_) {
                        if (_selectedTabbar == 0) {
                          return Container();//1st custom tabBarView
                        } else if (_selectedTabbar == 1) {
                          return Container();//2nd tabView
                        } else {
                          return Container(); //3rd tabView
                        }
                      }),
]
)
Shashwat Aditya
  • 390
  • 2
  • 11
Shazaib Danish
  • 264
  • 3
  • 7
  • 2
    Thank you! I did this but with an AnimatedSwitcher and it all works great now! – tapizquent May 28 '22 at 19:19
  • @tapizquent I'm trying to do the same thing with an AnimatedSwitcher to get the sliding effect between the tabs content, do you mind showing the code for how you solved it with the AnimatedSwitcher? – Majoren Jun 21 '22 at 15:14
  • It is better to use IndexedStack as it provides the index property instead of ifs in the builder – Litva Nick Aug 06 '23 at 12:22
8

Working Solution: you need to have expanded in two place

  1. Above DefaultTabController.
  2. Container of Containers.
Expanded(
    child: Container(
        decoration: BoxDecoration(
          border: Border(
              top: BorderSide(
                  color: Colors.grey, width: 0.5))),
        child: TabBarView(
          children: <Widget>[
              Container(),
              Container(),
              Container(),
        ])),),

One is not enough.

AinulBedjo
  • 55
  • 2
  • 10
3

1- You need to add your content into a list

2- Then put your tabBarView into a container

3- Assuming your items have a fixed height, define height of the container as height of your widget and just multiply by your current index content or list length

Ex: height: 90*_items.length.toDouble(),

Sam-kessy
  • 31
  • 3
3

The default TabBarView does not allows to have children with dynamic height. This package tried to solve that by extending default TabBarView and make required changes. AutoScaleTabbarView allows to have children with dynamic height. You can use this autoscale_tabbarview library to solve your case.

first, run this command in your terminal

flutter pub add autoscale_tabbarview

then, Import it to your dart file

import 'package:autoscale_tabbarview/autoscale_tabbarview.dart';

finally, replace TabBarView with AutoScaleTabbarView and remove height from your container

rizqanmr
  • 31
  • 1
1

I use this package: https://pub.dev/packages/autoscale_tabbarview by use this it can use TabBarView inside SingleChildScrollView

import 'package:autoscale_tabbarview/autoscale_tabbarview.dart';

AutoScaleTabBarView( // replace TabBarView with AutoScaleTabBarView
    children: myTabs.map((Tab tab) {
      final String label = tab.text!.toLowerCase();
      return Center(
        child: Text(
          'This is the $label tab',
          style: const TextStyle(fontSize: 36),
        ),
      );
    }).toList(),
  ),
MaKham
  • 39
  • 4
0

Wrap your listview with an Expanded widget:

Using an Expanded widget makes a child of a Row, Column, or Flex expand to fill the available space in the main axis (e.g., horizontally for a Row or vertically for a Column). If multiple children are expanded, the available space is divided among them according to the flex factor.

Expanded(
  child: ListView.builder(
  itemCount: 120,
  itemExtent: 32,
  itemBuilder: (BuildContext context, int index) {
    return new Row(
      children: <Widget>[
        Icon(Icons.access_alarm),
        Text(' Entry $index'),
      ],
    );
  },
))
Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
David
  • 15,894
  • 22
  • 55
  • 66
0

I had same requirement but need to show tab bar with dynamic height in bottom sheet, so I tried below code and it worked perfectly fine with me

class BottomSheetScreen extends StatefulWidget {
  @override
  _BottomSheetScreenState createState() => _BottomSheetScreenState();
}

class _BottomSheetScreenState extends State<BottomSheetScreen>
    with SingleTickerProviderStateMixin {

  late TabController _tabController;
  int _selectedTabbar = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ElevatedButton(
        child: const Text('showModalBottomSheet'),
        onPressed: () {
          showModalBottomSheet(
            context: context,
            builder: (BuildContext context) {
              return StatefulBuilder(
                  builder: (BuildContext context, StateSetter setState) {
                return Wrap(
                  children: [
                    Column(children: [
                      TabBar(
                        controller: _tabController,
                        onTap: (index) {
                          print(index);
                          setState(() {
                            _selectedTabbar = index;
                          });
                        },
                        tabs: [
                          Tab(text: 'Tab1'),
                          Tab(text: 'Tab2'),
                          Tab(text: 'Tab3'),
                        ],
                      ),
                      IntrinsicHeight(
                        child: Column(
                          children: [
                            _selectedTabbar == 0 ? Text("Tab1 content") : _selectedTabbar == 1 ? Text("Tab2 content") : Text("Tab3 content"),
                          ],
                        ),
                      ),
                      SizedBox(height: 10),
                      Text("bottom content"),
                    ]),
                  ],
                );
              });
            },
          );
        },
      ),
    );
  }
}
apurv thakkar
  • 8,608
  • 3
  • 14
  • 19
0

Wrap tabbarview with expanded widget to avoid fixed height of Container