stuckedunderflow
stuckedunderflow

Reputation: 3767

TabBarView with dynamic Container height

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)

Upvotes: 53

Views: 59973

Answers (9)

MaKham
MaKham

Reputation: 119

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(),
  ),

Upvotes: 7

Shahul Hameed
Shahul Hameed

Reputation: 1

Wrap tabbarview with expanded widget to avoid fixed height of Container

Upvotes: 0

rizqanmr
rizqanmr

Reputation: 71

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

Upvotes: 7

Apurv Thakkar
Apurv Thakkar

Reputation: 10158

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"),
                    ]),
                  ],
                );
              });
            },
          );
        },
      ),
    );
  }
}

Upvotes: 1

Sam-kessy
Sam-kessy

Reputation: 31

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(),

Upvotes: 3

Ashutosh Tiwari
Ashutosh Tiwari

Reputation: 103

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.

Upvotes: 11

Shazaib Danish
Shazaib Danish

Reputation: 345

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
                        }
                      }),
]
)

Upvotes: 35

Kherel
Kherel

Reputation: 16185

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.

Upvotes: 102

David
David

Reputation: 16277

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'),
      ],
    );
  },
))

Upvotes: 0

Related Questions