Reputation: 3767
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
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
Reputation: 1
Wrap tabbarview with expanded widget to avoid fixed height of Container
Upvotes: 0
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
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
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
Reputation: 103
Working Solution: you need to have expanded in two place
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
Reputation: 345
If you are getting error with Expanded also, then don't use TabBarView create custom TabBarView
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
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)],
),
),
),
);
}
}
Another way to get what you want could be creating custom TabView
widget.
Upvotes: 102
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